Handle conflicting types in BundleScanner 80/2280/4
authorPrasanth Pallamreddy <ppallamr@cisco.com>
Wed, 30 Oct 2013 19:34:03 +0000 (12:34 -0700)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 1 Nov 2013 16:52:10 +0000 (16:52 +0000)
  - Change precedence order for JAXB types
  - Add a validation check to report conflicting types
  - Add exclude capability to explicitly exclude types

Change-Id: I734be4ea72100d59ddc02d26072abad2dc5b0e9e
Signed-off-by: Prasanth Pallamreddy <ppallamr@cisco.com>
opendaylight/northbound/bundlescanner/api/src/main/java/org/opendaylight/controller/northbound/bundlescanner/IBundleScanService.java
opendaylight/northbound/bundlescanner/implementation/src/main/java/org/opendaylight/controller/northbound/bundlescanner/internal/BundleInfo.java
opendaylight/northbound/bundlescanner/implementation/src/main/java/org/opendaylight/controller/northbound/bundlescanner/internal/BundleScanServiceImpl.java
opendaylight/northbound/bundlescanner/implementation/src/main/java/org/opendaylight/controller/northbound/bundlescanner/internal/BundleScanner.java
opendaylight/northbound/bundlescanner/implementation/src/test/java/org/opendaylight/controller/northbound/bundlescanner/internal/BundleScannerTest.java
opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/NorthboundApplication.java

index 5cb0b63f1cc0b9895205cf582b6e2198f8add78e..dde205acd629148a8ce4beda31a1b73e0711211f 100644 (file)
@@ -9,6 +9,7 @@
 package org.opendaylight.controller.northbound.bundlescanner;
 
 import java.util.List;
 package org.opendaylight.controller.northbound.bundlescanner;
 
 import java.util.List;
+import java.util.Set;
 
 import org.osgi.framework.BundleContext;
 
 
 import org.osgi.framework.BundleContext;
 
@@ -30,5 +31,6 @@ public interface IBundleScanService {
     public List<Class<?>> getAnnotatedClasses(
             BundleContext context,
             String[] annotations,
     public List<Class<?>> getAnnotatedClasses(
             BundleContext context,
             String[] annotations,
+            Set<String> excludes,
             boolean includeDependentBundleClasses);
 }
             boolean includeDependentBundleClasses);
 }
index 4e94c5f84549344613ffe168386312f01a5de269..9cb1cb52ed36e0e8d2de80b84cc59620eb9f5430 100644 (file)
@@ -62,14 +62,14 @@ import org.slf4j.LoggerFactory;
         return bundle.getBundleId();
     }
 
         return bundle.getBundleId();
     }
 
-    public List<Class<?>> getAnnotatedClasses(Pattern pattern) {
+    public List<Class<?>> getAnnotatedClasses(Pattern pattern, Set<String> excludes) {
         List<String> result = new ArrayList<String>();
         for (Map.Entry<String, Set<String>> entry : annotatedClasses.entrySet()) {
             if (matches(pattern, entry.getValue())) {
                 result.add(entry.getKey());
             }
         }
         List<String> result = new ArrayList<String>();
         for (Map.Entry<String, Set<String>> entry : annotatedClasses.entrySet()) {
             if (matches(pattern, entry.getValue())) {
                 result.add(entry.getKey());
             }
         }
-        return BundleScanner.loadClasses(result, bundle);
+        return BundleScanner.loadClasses(result, bundle, excludes);
     }
 
     private boolean matches(Pattern pattern, Set<String> values) {
     }
 
     private boolean matches(Pattern pattern, Set<String> values) {
@@ -87,16 +87,18 @@ import org.slf4j.LoggerFactory;
      * @param allbundles - all bundles
      * @param pattern - annotation pattern to match
      * @param initBundle - the bundle which initiated this call
      * @param allbundles - all bundles
      * @param pattern - annotation pattern to match
      * @param initBundle - the bundle which initiated this call
+     * @param excludes - set of class names to be excluded
      *
      * @return list of annotated classes matching the pattern
      */
     public List<Class<?>> getAnnotatedClasses(
             Collection<BundleInfo> allbundles,
      *
      * @return list of annotated classes matching the pattern
      */
     public List<Class<?>> getAnnotatedClasses(
             Collection<BundleInfo> allbundles,
-            Pattern pattern, Bundle initBundle)
+            Pattern pattern, Bundle initBundle,
+            Set<String> excludes)
     {
     {
-        List<Class<?>> classes = getAnnotatedClasses(pattern);
+        List<Class<?>> classes = getAnnotatedClasses(pattern, excludes);
         processAnnotatedClassesInternal(this, allbundles, pattern,
         processAnnotatedClassesInternal(this, allbundles, pattern,
-                new HashSet<BundleInfo>(), classes, initBundle);
+                new HashSet<BundleInfo>(), classes, initBundle, excludes);
         return classes;
     }
 
         return classes;
     }
 
@@ -125,17 +127,18 @@ import org.slf4j.LoggerFactory;
             Pattern pattern,
             Collection<BundleInfo> visited,
             List<Class<?>> classes,
             Pattern pattern,
             Collection<BundleInfo> visited,
             List<Class<?>> classes,
-            Bundle initBundle)
+            Bundle initBundle, Set<String> excludes)
     {
         for (BundleInfo other : bundlesToScan) {
             if (other.getId() == target.getId()) continue;
             if (target.isDependantOn(other)) {
                 if (!visited.contains(other)) {
                     classes.addAll(BundleScanner.loadClasses(
     {
         for (BundleInfo other : bundlesToScan) {
             if (other.getId() == target.getId()) continue;
             if (target.isDependantOn(other)) {
                 if (!visited.contains(other)) {
                     classes.addAll(BundleScanner.loadClasses(
-                            other.getExportedAnnotatedClasses(pattern), initBundle));
+                            other.getExportedAnnotatedClasses(pattern),
+                            initBundle, excludes));
                     visited.add(other);
                     processAnnotatedClassesInternal(other, bundlesToScan,
                     visited.add(other);
                     processAnnotatedClassesInternal(other, bundlesToScan,
-                            pattern, visited, classes, initBundle);
+                            pattern, visited, classes, initBundle, excludes);
                 }
             }
         }
                 }
             }
         }
index ad6d8e9a1754ccdac77fe2119a88ccf3b09f34b7..751b597e787de4a22fc566d838cd7015b734fdea 100644 (file)
@@ -1,6 +1,7 @@
 package org.opendaylight.controller.northbound.bundlescanner.internal;
 
 import java.util.List;
 package org.opendaylight.controller.northbound.bundlescanner.internal;
 
 import java.util.List;
+import java.util.Set;
 
 import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService;
 import org.osgi.framework.BundleContext;
 
 import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService;
 import org.osgi.framework.BundleContext;
@@ -13,10 +14,11 @@ public class BundleScanServiceImpl implements IBundleScanService {
     @Override
     public List<Class<?>> getAnnotatedClasses(BundleContext context,
             String[] annotations,
     @Override
     public List<Class<?>> getAnnotatedClasses(BundleContext context,
             String[] annotations,
+            Set<String> excludes,
             boolean includeDependentBundleClasses)
     {
         return BundleScanner.getInstance().getAnnotatedClasses(
             boolean includeDependentBundleClasses)
     {
         return BundleScanner.getInstance().getAnnotatedClasses(
-                context, annotations, includeDependentBundleClasses);
+                context, annotations, excludes, includeDependentBundleClasses);
     }
 
 }
     }
 
 }
index a5a2073a610886d0aed68044ca70f40c44146556..0c640820598b1c4f12f7f786df1ee3f23ee774a2 100644 (file)
@@ -22,6 +22,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
 
 import java.util.Set;
 import java.util.regex.Pattern;
 
+import javax.xml.bind.annotation.XmlRootElement;
+
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
@@ -66,6 +68,7 @@ import org.slf4j.LoggerFactory;
 
     public List<Class<?>> getAnnotatedClasses(BundleContext context,
             String[] annotations,
 
     public List<Class<?>> getAnnotatedClasses(BundleContext context,
             String[] annotations,
+            Set<String> excludes,
             boolean includeDependentBundleClasses)
     {
         BundleInfo info = bundleAnnotations.get(context.getBundle().getBundleId());
             boolean includeDependentBundleClasses)
     {
         BundleInfo info = bundleAnnotations.get(context.getBundle().getBundleId());
@@ -74,9 +77,14 @@ import org.slf4j.LoggerFactory;
         List<Class<?>> result = null;
         if (includeDependentBundleClasses) {
             result = info.getAnnotatedClasses(bundleAnnotations.values(),
         List<Class<?>> result = null;
         if (includeDependentBundleClasses) {
             result = info.getAnnotatedClasses(bundleAnnotations.values(),
-                    pattern, context.getBundle());
+                    pattern, context.getBundle(), excludes);
+            // reverse the list to give precedence to the types loaded from the
+            // initiating bundle
+            Collections.reverse(result);
+            // validate for conflicts only when searching dependencies
+            validate(result);
         } else {
         } else {
-            result = info.getAnnotatedClasses(pattern);
+            result = info.getAnnotatedClasses(pattern, excludes);
         }
         LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
         return result;
         }
         LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
         return result;
@@ -240,12 +248,13 @@ import org.slf4j.LoggerFactory;
 
     public static List<Class<?>> loadClasses(
             Collection<String> annotatedClasses,
 
     public static List<Class<?>> loadClasses(
             Collection<String> annotatedClasses,
-            Bundle initBundle)
+            Bundle initBundle, Set<String> excludes)
     {
         List<Class<?>> result = new ArrayList<Class<?>>();
         StringBuilder errors = new StringBuilder();
         for (String name : annotatedClasses) {
             try {
     {
         List<Class<?>> result = new ArrayList<Class<?>>();
         StringBuilder errors = new StringBuilder();
         for (String name : annotatedClasses) {
             try {
+                if (excludes != null && excludes.contains(name)) continue;
                 result.add(initBundle.loadClass(name));
             } catch (ClassNotFoundException e) {
                 errors.append(name).append(", ");
                 result.add(initBundle.loadClass(name));
             } catch (ClassNotFoundException e) {
                 errors.append(name).append(", ");
@@ -281,4 +290,31 @@ import org.slf4j.LoggerFactory;
         return Pattern.compile(regex.toString());
     }
 
         return Pattern.compile(regex.toString());
     }
 
+    private void validate(List<Class<?>> classes) {
+        if (classes == null || classes.size() == 0) return;
+        Map<String,String> names = new HashMap<String,String>();
+        StringBuilder conflictsMsg = new StringBuilder();
+        for (Class c : classes) {
+            XmlRootElement root = (XmlRootElement) c.getAnnotation(XmlRootElement.class);
+            if (root == null) continue;
+            String rootName = root.name();
+            if ("##default".equals(rootName)) {
+                String clsName = c.getSimpleName();
+                rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1);
+            }
+            String other = names.get(rootName);
+            if (other != null && !other.equals(c.getName())) {
+                conflictsMsg.append(System.lineSeparator())
+                    .append("[").append(rootName).append(":")
+                    .append(c.getName()).append(",").append(other)
+                    .append("]");
+            } else {
+                names.put(rootName, c.getName());
+            }
+        }
+        if (conflictsMsg.length() > 0) {
+            LOGGER.info("JAXB type conflicts detected : {}", conflictsMsg.toString());
+        }
+    }
+
 }
 }
index 670ba9eae151f67905fb3747f1966f7e86cb61ff..38d361c824880179cd7875f59c3f4449f204b2ad 100644 (file)
@@ -11,8 +11,10 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 import org.junit.AfterClass;
 import java.util.regex.Pattern;
 
 import org.junit.AfterClass;
@@ -63,18 +65,18 @@ public class BundleScannerTest {
     public void testBundleEvents() throws Exception {
         MockBundle newBundle = new TestMockBundle("misc", "", "bundle_misc");
         assertTrue(bundleScanner.getAnnotatedClasses(
     public void testBundleEvents() throws Exception {
         MockBundle newBundle = new TestMockBundle("misc", "", "bundle_misc");
         assertTrue(bundleScanner.getAnnotatedClasses(
-                newBundle.getBundleContext(), null, false).size() == 0);
+                newBundle.getBundleContext(), null, null, false).size() == 0);
         BundleEvent event = new BundleEvent(BundleEvent.RESOLVED, newBundle);
         bundleScanner.bundleChanged(event);
         assertTrue(bundleScanner.getAnnotatedClasses(
         BundleEvent event = new BundleEvent(BundleEvent.RESOLVED, newBundle);
         bundleScanner.bundleChanged(event);
         assertTrue(bundleScanner.getAnnotatedClasses(
-                newBundle.getBundleContext(), null, false).size() == 1);
+                newBundle.getBundleContext(), null, null, false).size() == 1);
     }
 
     @Test
     public void testAnnotatedClassesWithDependencies() throws Exception {
         for (Bundle bundle : bundles) {
             List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
     }
 
     @Test
     public void testAnnotatedClassesWithDependencies() throws Exception {
         for (Bundle bundle : bundles) {
             List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
-                    bundle.getBundleContext(), null, true);
+                    bundle.getBundleContext(), null, null, true);
             String name = bundle.getSymbolicName();
             System.out.println("name:" + name + " classes:" + classes.size());
             if ("misc".equals(name)) {
             String name = bundle.getSymbolicName();
             System.out.println("name:" + name + " classes:" + classes.size());
             if ("misc".equals(name)) {
@@ -94,7 +96,7 @@ public class BundleScannerTest {
         Bundle bundle = findBundle("sub1");
         String[] annos = { "javax.xml.bind.annotation.XmlTransient" };
         List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
         Bundle bundle = findBundle("sub1");
         String[] annos = { "javax.xml.bind.annotation.XmlTransient" };
         List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
-                bundle.getBundleContext(), annos, true);
+                bundle.getBundleContext(), annos, null, true);
         assertTrue(classes.size() == 1);
     }
 
         assertTrue(classes.size() == 1);
     }
 
@@ -103,7 +105,7 @@ public class BundleScannerTest {
         Bundle bundle = findBundle("sub1");
         String[] annos = { "javax.xml.bind.annotation.*" };
         List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
         Bundle bundle = findBundle("sub1");
         String[] annos = { "javax.xml.bind.annotation.*" };
         List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
-                bundle.getBundleContext(), annos, true);
+                bundle.getBundleContext(), annos, null, true);
         assertTrue(classes.size() == 6);
     }
 
         assertTrue(classes.size() == 6);
     }
 
@@ -112,7 +114,7 @@ public class BundleScannerTest {
         Bundle bundle = findBundle("sub1");
         String[] annos = { "non.existent.pkg" };
         List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
         Bundle bundle = findBundle("sub1");
         String[] annos = { "non.existent.pkg" };
         List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
-                bundle.getBundleContext(), annos, true);
+                bundle.getBundleContext(), annos, null, true);
         assertTrue(classes.size() == 0);
     }
 
         assertTrue(classes.size() == 0);
     }
 
@@ -129,6 +131,17 @@ public class BundleScannerTest {
         assertFalse(pattern.matcher("Ljavax/servlet/FOO;").find());
     }
 
         assertFalse(pattern.matcher("Ljavax/servlet/FOO;").find());
     }
 
+    @Test
+    public void testExclude() {
+        Set<String> excludes = new HashSet<String>();
+        excludes.add("bundle_base.Animal");
+        Bundle bundle = findBundle("sub1");
+        String[] annos = { "javax.xml.bind.annotation.*" };
+        List<Class<?>> classes = bundleScanner.getAnnotatedClasses(
+                bundle.getBundleContext(), annos, excludes, true);
+        assertTrue(classes.size() == 5);
+    }
+
     private static Bundle findBundle(String symName) {
         for (Bundle bundle : bundles) {
             if (bundle.getSymbolicName().equals(symName)) return bundle;
     private static Bundle findBundle(String symName) {
         for (Bundle bundle : bundles) {
             if (bundle.getSymbolicName().equals(symName)) return bundle;
index 5b8219126b8abbb7c82124b22f502505c2791e8c..e97a562620771ddf8d1a9bab0e873a89a58dc0b2 100644 (file)
@@ -39,7 +39,23 @@ import org.slf4j.LoggerFactory;
 @SuppressWarnings("unchecked")
 public class NorthboundApplication extends Application {
     public static final String JAXRS_RESOURCES_MANIFEST_NAME = "Jaxrs-Resources";
 @SuppressWarnings("unchecked")
 public class NorthboundApplication extends Application {
     public static final String JAXRS_RESOURCES_MANIFEST_NAME = "Jaxrs-Resources";
+    public static final String JAXRS_EXCLUDES_MANIFEST_NAME = "Jaxrs-Exclude-Types";
     private static final Logger LOGGER = LoggerFactory.getLogger(NorthboundApplication.class);
     private static final Logger LOGGER = LoggerFactory.getLogger(NorthboundApplication.class);
+    private final Set<Object> _singletons;
+
+    public NorthboundApplication() {
+        _singletons = new HashSet<Object>();
+        _singletons.add(new ContextResolver<JAXBContext>() {
+            JAXBContext jaxbContext = newJAXBContext();
+            @Override
+            public JAXBContext getContext(Class<?> type) {
+                return jaxbContext;
+            }
+
+        } );
+        _singletons.add(getJsonProvider());
+        _singletons.add(new JacksonJsonProcessingExceptionMapper());
+    }
 
     ////////////////////////////////////////////////////////////////
     //  Application overrides
 
     ////////////////////////////////////////////////////////////////
     //  Application overrides
@@ -47,17 +63,7 @@ public class NorthboundApplication extends Application {
 
     @Override
     public Set<Object> getSingletons() {
 
     @Override
     public Set<Object> getSingletons() {
-        Set<Object> singletons = new HashSet<Object>();
-        singletons.add(new ContextResolver<JAXBContext>() {
-            @Override
-            public JAXBContext getContext(Class<?> type) {
-                return newJAXBContext();
-            }
-
-        } );
-        singletons.add(getJsonProvider());
-        singletons.add(new JacksonJsonProcessingExceptionMapper());
-        return singletons;
+        return _singletons;
     }
 
     @Override
     }
 
     @Override
@@ -102,6 +108,7 @@ public class NorthboundApplication extends Application {
         try {
             List<Class<?>> cls = svc.getAnnotatedClasses(ctx,
                     new String[] { XmlRootElement.class.getPackage().getName() },
         try {
             List<Class<?>> cls = svc.getAnnotatedClasses(ctx,
                     new String[] { XmlRootElement.class.getPackage().getName() },
+                    parseManifestEntry(ctx, JAXRS_EXCLUDES_MANIFEST_NAME),
                     true);
             return JAXBContext.newInstance(cls.toArray(new Class[cls.size()]));
         } catch (JAXBException je) {
                     true);
             return JAXBContext.newInstance(cls.toArray(new Class[cls.size()]));
         } catch (JAXBException je) {
@@ -118,29 +125,22 @@ public class NorthboundApplication extends Application {
         try {
             IBundleScanService svc = lookupBundleScanner(ctx);
             result.addAll(svc.getAnnotatedClasses(ctx,
         try {
             IBundleScanService svc = lookupBundleScanner(ctx);
             result.addAll(svc.getAnnotatedClasses(ctx,
-                    new String[] { javax.ws.rs.Path.class.getName() }, false));
+                    new String[] { javax.ws.rs.Path.class.getName() },
+                    null, false));
         } catch (ServiceException se) {
             recordException = se;
             LOGGER.debug("Error finding JAXRS resource annotated classes in " +
                     "bundle: {} error: {}.", bundleName, se.getMessage());
             // the bundle scan service cannot be lookedup. Lets attempt to
             // lookup the resources from the bundle manifest header
         } catch (ServiceException se) {
             recordException = se;
             LOGGER.debug("Error finding JAXRS resource annotated classes in " +
                     "bundle: {} error: {}.", bundleName, se.getMessage());
             // the bundle scan service cannot be lookedup. Lets attempt to
             // lookup the resources from the bundle manifest header
-            Dictionary<String,String> headers = ctx.getBundle().getHeaders();
-            String header = headers.get(JAXRS_RESOURCES_MANIFEST_NAME);
-            if (header != null) {
-                for (String s : header.split(",")) {
-                    s = s.trim();
-                    if (s.length() > 0) {
-                        try {
-                            result.add(ctx.getBundle().loadClass(s));
-                        } catch (ClassNotFoundException cnfe) {
-                            LOGGER.error("Cannot load class: {} in bundle: {} " +
-                                    "defined as MANIFEST JAX-RS resource", s, bundleName, cnfe);
-                        }
-                    }
+            for (String c : parseManifestEntry(ctx, JAXRS_RESOURCES_MANIFEST_NAME)) {
+                try {
+                    result.add(ctx.getBundle().loadClass(c));
+                } catch (ClassNotFoundException cnfe) {
+                    LOGGER.error("Cannot load class: {} in bundle: {} " +
+                            "defined as MANIFEST JAX-RS resource", c, bundleName, cnfe);
                 }
             }
                 }
             }
-
         }
 
         if (result.size() == 0) {
         }
 
         if (result.size() == 0) {
@@ -154,4 +154,19 @@ public class NorthboundApplication extends Application {
         return result;
     }
 
         return result;
     }
 
+    private final Set<String> parseManifestEntry(BundleContext ctx, String name) {
+        Set<String> result = new HashSet<String>();
+        Dictionary<String,String> headers = ctx.getBundle().getHeaders();
+        String header = headers.get(name);
+        if (header != null) {
+            for (String s : header.split(",")) {
+                s = s.trim();
+                if (s.length() > 0) {
+                    result.add(s);
+                }
+            }
+        }
+        return result;
+    }
+
 }
 }