You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:20:38 UTC

[sling-org-apache-sling-testing-osgi-mock] 02/06: SLING-6586 Loading SCR metadata fails for components with name != class name

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.osgi-mock-1.9.4
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-osgi-mock.git

commit 38aed31e906b64b2c722c15e966522fea8016ef2
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Wed Mar 1 17:29:06 2017 +0000

    SLING-6586 Loading SCR metadata fails for components with name != class name
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/branches/testing/mocks/osgi-mock-1.x@1785004 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  31 +++
 .../testing/mock/osgi/NoScrMetadataException.java  |   2 +-
 .../sling/testing/mock/osgi/OsgiMetadataUtil.java  | 277 ++++++++++++++++-----
 3 files changed, 242 insertions(+), 68 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9db0b71..e6f1f5c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,6 +94,37 @@
         </dependency>
     
         <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <!-- Do not use version 0.9.10, it created threading issues when running junit tests in parallel (see SLING-5002) -->
+            <version>0.9.9</version>
+            <scope>compile</scope>
+            <!-- exclude all optional dependencies -->
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>dom4j</groupId>
+                    <artifactId>dom4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.google.code.gson</groupId>
+                    <artifactId>gson</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
             <version>1.9.5</version>
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/NoScrMetadataException.java b/src/main/java/org/apache/sling/testing/mock/osgi/NoScrMetadataException.java
index 97b9260..0e9114d 100644
--- a/src/main/java/org/apache/sling/testing/mock/osgi/NoScrMetadataException.java
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/NoScrMetadataException.java
@@ -25,7 +25,7 @@ public final class NoScrMetadataException extends RuntimeException {
     private static final long serialVersionUID = 1L;
 
     public NoScrMetadataException(Class<?> type) {
-        super("No OSGi SCR metadata found in classpath at " + OsgiMetadataUtil.getMetadataPath(type));
+        super("No OSGi SCR metadata found for class " + OsgiMetadataUtil.cleanupClassName(type.getName()));
     }
 
 }
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
index 21443f5..c209bab 100644
--- a/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
@@ -20,9 +20,7 @@ package org.apache.sling.testing.mock.osgi;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URL;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -30,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
 
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.parsers.DocumentBuilder;
@@ -37,10 +36,17 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
 import javax.xml.xpath.XPathExpressionException;
 import javax.xml.xpath.XPathFactory;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.framework.FilterImpl;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.reflections.Reflections;
+import org.reflections.scanners.ResourcesScanner;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -53,7 +59,6 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableList;
 
 /**
  * Helper methods to parse OSGi metadata.
@@ -61,11 +66,16 @@ import com.google.common.collect.ImmutableList;
 final class OsgiMetadataUtil {
 
     private static final Logger log = LoggerFactory.getLogger(OsgiMetadataUtil.class);
+    
+    private static final String METADATA_PATH = "OSGI-INF";
 
     private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
     static {
         DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
         DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true);
+
+        // suppress log entries from Reflections library
+        Reflections.log = null;
     }
 
     private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
@@ -76,30 +86,7 @@ final class OsgiMetadataUtil {
     }
 
     private static final OsgiMetadata NULL_METADATA = new OsgiMetadata();
-
-    /*
-     * The OSGI metadata XML files do not change during the unit test runs because static part of classpath.
-     * So we can cache the parsing step if we need them multiple times.
-     */
-    private static final LoadingCache<Class, OsgiMetadata> METADATA_CACHE = CacheBuilder.newBuilder().build(new CacheLoader<Class, OsgiMetadata>() {
-        @Override
-        public OsgiMetadata load(Class clazz) throws Exception {
-            List<Document> metadataDocuments = OsgiMetadataUtil.getMetadataDocument(clazz);
-            if (metadataDocuments != null) {
-                for (Document metadataDocument : metadataDocuments) {
-                    if (matchesService(clazz, metadataDocument)) {
-                        return new OsgiMetadata(clazz, metadataDocument);
-                    }
-                }
-            }
-            return NULL_METADATA;
-        }
-    });
-
-    private OsgiMetadataUtil() {
-        // static methods only
-    }
-
+    
     private static final NamespaceContext NAMESPACE_CONTEXT = new NamespaceContext() {
         @Override
         public String getNamespaceURI(String prefix) {
@@ -117,14 +104,26 @@ final class OsgiMetadataUtil {
         }
     };
 
-    public static String getMetadataPath(Class clazz) {
-        return "OSGI-INF/" + StringUtils.substringBefore(clazz.getName(), "$") + ".xml";
-    }
+    /*
+     * The OSGI metadata XML files do not change during the unit test runs because static part of classpath.
+     * So we can cache the parsing step if we need them multiple times.
+     */
+    private static final Map<String,Document> METADATA_DOCUMENT_CACHE = initMetadataDocumentCache();
+    private static final LoadingCache<Class, OsgiMetadata> METADATA_CACHE = CacheBuilder.newBuilder().build(new CacheLoader<Class, OsgiMetadata>() {
+        @Override
+        public OsgiMetadata load(Class clazz) throws Exception {
+            Document metadataDocument = METADATA_DOCUMENT_CACHE.get(cleanupClassName(clazz.getName()));
+            if (metadataDocument != null) {
+                return new OsgiMetadata(clazz, metadataDocument);
+            }
+            return NULL_METADATA;
+        }
+    });
 
-    public static String getOldMetadataMultiPath() {
-        return "OSGI-INF/serviceComponents.xml";
+    private OsgiMetadataUtil() {
+        // static methods only
     }
-
+    
     /**
      * Try to read OSGI-metadata from /OSGI-INF and read all implemented interfaces and service properties.
      * The metadata is cached after initial read, so it's no problem to call this method multiple time for the same class.
@@ -145,36 +144,79 @@ final class OsgiMetadataUtil {
             throw new RuntimeException("Error loading OSGi metadata from loader cache.", ex);
         }
     }
+    
+    /**
+     * Reads all SCR metadata XML documents located at OSGI-INF/ and caches them with quick access by implementation class.
+     * @return Cache map
+     */
+    private static Map<String,Document> initMetadataDocumentCache() {
+        Map<String,Document> cacheMap = new HashMap<String,Document>();
+        
+        XPath xpath = XPATH_FACTORY.newXPath();
+        xpath.setNamespaceContext(NAMESPACE_CONTEXT);
+        XPathExpression xpathExpression;
+        try {
+            xpathExpression = xpath.compile("//*[implementation/@class]");
+        }
+        catch (XPathExpressionException ex) {
+            throw new RuntimeException("Compiling XPath expression failed.", ex);
+        }
 
-    private static List<Document> getMetadataDocument(Class clazz) {
-        String metadataPath = getMetadataPath(clazz);
-        InputStream metadataStream = OsgiMetadataUtil.class.getClassLoader().getResourceAsStream(metadataPath);
-        if (metadataStream == null) {
-            String oldMetadataPath = getOldMetadataMultiPath();
-            log.debug("No OSGi metadata found at {}, try to fallback to {}", metadataPath, oldMetadataPath);
-
-            try {
-                Enumeration<URL> metadataUrls = OsgiMetadataUtil.class.getClassLoader().getResources(oldMetadataPath);
-                List<Document> docs = new ArrayList<Document>();
-                while (metadataUrls.hasMoreElements()) {
-                    URL metadataUrl = metadataUrls.nextElement();
-                    metadataStream = metadataUrl.openStream();
-                    docs.add(toXmlDocument(metadataStream, oldMetadataPath));
-                }
-                if (docs.size() == 0) {
-                    return null;
+        Reflections reflections = new Reflections(METADATA_PATH, new ResourcesScanner());
+        Set<String> paths = reflections.getResources(Pattern.compile("^.*\\.xml$"));
+        for (String path : paths) {
+            parseMetadataDocuments(cacheMap, path, xpathExpression);
+        }
+        
+        return cacheMap;
+    }
+    
+    private static void parseMetadataDocuments(Map<String,Document> cacheMap, String resourcePath, XPathExpression xpathExpression) {
+        InputStream fileStream = null;
+        try {
+            fileStream = OsgiMetadataUtil.class.getClassLoader().getResourceAsStream(resourcePath);
+            if (fileStream != null) {
+                Document metadata = toXmlDocument(fileStream, resourcePath);
+                NodeList nodes = (NodeList)xpathExpression.evaluate(metadata, XPathConstants.NODESET);
+                if (nodes != null) {
+                    for (int i = 0; i < nodes.getLength(); i++) {
+                        Node node = nodes.item(i);
+                        String implementationClass = getImplementationClassName(node);
+                        if (implementationClass != null) {
+                            cacheMap.put(implementationClass, metadata);
+                        }
+                    }
+                }                            
+            }
+        }
+        catch (Throwable ex) {
+            log.warn("Error reading SCR metadata XML document from " + resourcePath, ex);
+        }
+        finally {
+            if (fileStream != null) {
+                try {
+                    fileStream.close();
                 }
-                else {
-                    return docs;
+                catch (IOException e) {
+                    // ignore
                 }
             }
-            catch (IOException ex) {
-                throw new RuntimeException("Unable to read classpath resource: " + oldMetadataPath, ex);
-            }
         }
-        else {
-            return ImmutableList.of(toXmlDocument(metadataStream, metadataPath));
+    }
+    
+    private static String getImplementationClassName(Node componentNode) {
+        NodeList childNodes = componentNode.getChildNodes();
+        for (int j = 0; j < childNodes.getLength(); j++) {
+            Node childNode = childNodes.item(j);
+            if (childNode.getNodeName().equals("implementation")) {
+                String implementationClass = getAttributeValue(childNode, "class");
+                if (!StringUtils.isBlank(implementationClass)) {
+                    return implementationClass;
+                }
+                break;
+            }
         }
+        return null;
     }
 
     private static Document toXmlDocument(InputStream inputStream, String path) {
@@ -202,16 +244,19 @@ final class OsgiMetadataUtil {
      * @return XPath query fragment to find matching XML node in SCR metadata
      */
     private static String getComponentXPathQuery(Class clazz) {
-        String className = StringUtils.substringBefore(clazz.getName(), "$$Enhancer");
+        String className = cleanupClassName(clazz.getName());
         return "//*[implementation/@class='" + className + "' or @name='" + className + "']";
     }
-
-    private static boolean matchesService(Class clazz, Document metadata) {
-        String query = getComponentXPathQuery(clazz);
-        NodeList nodes = queryNodes(metadata, query);
-        return nodes != null && nodes.getLength() > 0;
+    
+    /**
+     * Remove extensions from class names added e.g. by mockito.
+     * @param className Class name
+     * @return Cleaned up class name
+     */
+    public static final String cleanupClassName(String className) {
+        return StringUtils.substringBefore(className, "$$Enhancer");
     }
-
+    
     private static Set<String> getServiceInterfaces(Class clazz, Document metadata) {
         Set<String> serviceInterfaces = new HashSet<String>();
         String query = getComponentXPathQuery(clazz) + "/service/provide[@interface!='']";
@@ -389,6 +434,10 @@ final class OsgiMetadataUtil {
         private final ReferencePolicyOption policyOption;
         private final String bind;
         private final String unbind;
+        private final String field;
+        private final FieldCollectionType fieldCollectionType;
+        private final String target;
+        private final Filter targetFilter;
 
         private Reference(Class<?> clazz, Node node) {
             this.clazz = clazz;
@@ -399,6 +448,19 @@ final class OsgiMetadataUtil {
             this.policyOption = toPolicyOption(getAttributeValue(node, "policy-option"));
             this.bind = getAttributeValue(node, "bind");
             this.unbind = getAttributeValue(node, "unbind");
+            this.field = getAttributeValue(node, "field");
+            this.fieldCollectionType = toFieldCollectionType(getAttributeValue(node, "field-collection-type"));
+            this.target = getAttributeValue(node, "target");
+            if (StringUtils.isNotEmpty(this.target)) {
+                try {
+                    this.targetFilter = new FilterImpl(this.target);
+                } catch (InvalidSyntaxException ex) {
+                    throw new RuntimeException("Invalid target filter in reference '" + this.name + "' of class " + clazz.getName(), ex);
+                }
+            }
+            else {
+                this.targetFilter = null;
+            }
         }
 
         public Class<?> getServiceClass() {
@@ -413,9 +475,27 @@ final class OsgiMetadataUtil {
             return this.interfaceType;
         }
 
+        public Class getInterfaceTypeAsClass() {
+            try {
+                return Class.forName(getInterfaceType());
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException("Service reference type not found: " + getInterfaceType());
+            }
+        }
+
         public ReferenceCardinality getCardinality() {
             return this.cardinality;
         }
+        
+        public boolean isCardinalityMultiple() {
+            return this.cardinality == ReferenceCardinality.OPTIONAL_MULTIPLE
+                    || this.cardinality == ReferenceCardinality.MANDATORY_MULTIPLE;
+        }
+
+        public boolean isCardinalityOptional() {
+            return this.cardinality == ReferenceCardinality.OPTIONAL_UNARY
+                    || this.cardinality == ReferenceCardinality.OPTIONAL_MULTIPLE;
+        }
 
         public ReferencePolicy getPolicy() {
             return policy;
@@ -424,7 +504,7 @@ final class OsgiMetadataUtil {
         public ReferencePolicyOption getPolicyOption() {
             return policyOption;
         }
-        
+
         public String getBind() {
             return this.bind;
         }
@@ -433,6 +513,25 @@ final class OsgiMetadataUtil {
             return this.unbind;
         }
 
+        public String getField() {
+            return this.field;
+        }
+        
+        public String getTarget() {
+            return this.target;
+        }
+        
+        public boolean matchesTargetFilter(ServiceReference serviceReference) {
+            if (targetFilter == null) {
+                return true;
+            }
+            return targetFilter.match(serviceReference);
+        }
+        
+        public FieldCollectionType getFieldCollectionType() {
+            return this.fieldCollectionType;
+        }
+
         private static ReferenceCardinality toCardinality(String value) {
             for (ReferenceCardinality item : ReferenceCardinality.values()) {
                 if (StringUtils.equals(item.getCardinalityString(), value)) {
@@ -459,7 +558,16 @@ final class OsgiMetadataUtil {
             }
             return ReferencePolicyOption.RELUCTANT;
         }
-        
+
+        private static FieldCollectionType toFieldCollectionType(String value) {
+            for (FieldCollectionType item : FieldCollectionType.values()) {
+                if (StringUtils.equalsIgnoreCase(item.name(), value)) {
+                    return item;
+                }
+            }
+            return FieldCollectionType.SERVICE;
+        }
+
     }
 
 
@@ -528,6 +636,7 @@ final class OsgiMetadataUtil {
         DYNAMIC;
     }
 
+
     /**
      * Options for {@link Reference#policyOption()} property.
      */
@@ -551,5 +660,39 @@ final class OsgiMetadataUtil {
          */
         GREEDY;
     }
-    
+
+    /**
+     * Options for {@link Reference#policyOption()} property.
+     */
+    enum FieldCollectionType {
+
+        /**
+         * The bound service object. This is the default field collection type.
+         */
+        SERVICE,
+
+        /**
+         * A Service Reference for the bound service.
+         */
+        REFERENCE,
+
+        /**
+         * A Component Service Objects for the bound service.
+         */
+        SERVICEOBJECTS,
+
+        /**
+         * An unmodifiable Map containing the service properties of the bound service.
+         * This Map must implement Comparable.
+         */
+        PROPERTIES,
+
+        /**
+         * An unmodifiable Map.Entry whose key is an unmodifiable Map containing the 
+         * service properties of the bound service, as above, and whose value is the 
+         * bound service object. This Map.Entry must implement Comparable.
+         */
+        TUPLE;
+    }
+
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.