2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.northbound.bundlescanner.internal;
11 import java.io.IOException;
12 import java.io.InputStream;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.Enumeration;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.List;
23 import java.util.regex.Pattern;
25 import javax.xml.bind.annotation.XmlRootElement;
27 import org.objectweb.asm.AnnotationVisitor;
28 import org.objectweb.asm.ClassReader;
29 import org.objectweb.asm.ClassVisitor;
30 import org.objectweb.asm.Opcodes;
31 import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService;
32 import org.osgi.framework.Bundle;
33 import org.osgi.framework.BundleContext;
34 import org.osgi.framework.BundleEvent;
35 import org.osgi.framework.FrameworkUtil;
36 import org.osgi.framework.SynchronousBundleListener;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The custom bundle scanner scans annotations on bundles and is used for
42 * constructing JAXBContext instances. It listens for bundle events and updates
43 * the metadata in realtime.
45 /*package*/ class BundleScanner implements SynchronousBundleListener {
46 private static final Logger LOGGER = LoggerFactory.getLogger(BundleScanner.class);
47 private static BundleScanner INSTANCE; // singleton
49 private final Pattern annotationPattern;
50 private final Map<Long,BundleInfo> bundleAnnotations =
51 new HashMap<Long, BundleInfo>();
53 public static synchronized BundleScanner getInstance() {
54 if (INSTANCE == null) {
55 INSTANCE = new BundleScanner();
60 /*package*/ BundleScanner(Bundle[] bundles) {
61 annotationPattern = mergePatterns(IBundleScanService.ANNOTATIONS_TO_SCAN, true);
65 /*package*/ BundleScanner() {
66 this(FrameworkUtil.getBundle(BundleScanner.class).getBundleContext().getBundles());
69 public List<Class<?>> getAnnotatedClasses(BundleContext context,
72 boolean includeDependentBundleClasses)
74 BundleInfo info = bundleAnnotations.get(context.getBundle().getBundleId());
75 if (info == null) return Collections.emptyList();
76 Pattern pattern = mergePatterns(annotations, false);
77 List<Class<?>> result = null;
78 if (includeDependentBundleClasses) {
79 result = info.getAnnotatedClasses(bundleAnnotations.values(),
80 pattern, context.getBundle(), excludes);
81 // reverse the list to give precedence to the types loaded from the
83 Collections.reverse(result);
84 // validate for conflicts only when searching dependencies
87 result = info.getAnnotatedClasses(pattern, excludes);
89 LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
93 ////////////////////////////////////////////////////////////////
94 // SynchronousBundleListener implementation
95 ////////////////////////////////////////////////////////////////
98 public void bundleChanged(BundleEvent event) {
99 Bundle bundle = event.getBundle();
100 long id = bundle.getBundleId();
101 switch(event.getType()) {
102 case BundleEvent.RESOLVED :
105 case BundleEvent.UNRESOLVED :
106 case BundleEvent.UNINSTALLED :
107 bundleAnnotations.remove(id);
113 ////////////////////////////////////////////////////////////////
114 // ClassVisitor implementation
115 ////////////////////////////////////////////////////////////////
117 private static class AnnotationDetector extends ClassVisitor {
118 private final Map<String, Set<String>> matchedClasses =
119 new HashMap<String, Set<String>>();
121 private final Pattern annotationsPattern;
122 private Set<String> annotations;
123 private String className;
124 private boolean accessible;
125 private boolean matchedAnnotation;
127 public AnnotationDetector(Pattern pattern) {
129 this.annotationsPattern = pattern;
132 public Map<String, Set<String>> getMatchedClasses() {
133 return new HashMap<String, Set<String>>(matchedClasses);
137 public void visit(int version, int access, String name, String signature,
138 String superName, String[] interfaces)
140 //LOGGER.debug("Visiting class:" + name);
142 accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
143 matchedAnnotation = false;
144 annotations = new HashSet<String>();
148 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
149 //LOGGER.debug("Visiting annotation:" + desc);
150 annotations.add(signature2class(desc));
151 if (!matchedAnnotation) {
152 matchedAnnotation = (annotationsPattern == null ||
153 annotationsPattern.matcher(desc).find());
159 public void visitEnd() {
160 if (matchedAnnotation && accessible) {
161 className = path2class(className);
162 matchedClasses.put(className, new HashSet<String>(annotations));
167 ////////////////////////////////////////////////////////////////
169 ////////////////////////////////////////////////////////////////
171 private synchronized void init(Bundle[] bundles) {
172 for (Bundle bundle : bundles) {
173 int state = bundle.getState();
174 if (state == Bundle.RESOLVED ||
175 state == Bundle.STARTING ||
176 state == Bundle.ACTIVE)
183 private static String path2class(String path) {
184 return path.replace(".class", "").replaceAll("/", ".");
187 private static String class2path(String clz) {
188 return clz.replaceAll("\\.", "/");
191 @SuppressWarnings("unused")
192 private static String class2signature(String clz) {
193 return "L" + class2path(clz) + ";";
196 private static String signature2class(String sig) {
197 if (sig.startsWith("L") && sig.endsWith(";")) {
198 sig = sig.substring(1, sig.length()-1);
200 return path2class(sig);
203 private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
204 List<URL> result = new ArrayList<URL>();
205 boolean recurse = false;
208 pkgs = new String[] { "/" } ;
210 for (String pkg : pkgs) {
211 pkg = class2path(pkg);
212 final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
214 while (e.hasMoreElements()) {
215 URL url = e.nextElement();
223 private synchronized void scan(Bundle bundle) {
224 AnnotationDetector detector = new AnnotationDetector(annotationPattern);
226 for (URL u : getBundleClasses(bundle, null)) {
227 InputStream is = u.openStream();
228 new ClassReader(is).accept(detector, 0);
231 } catch (IOException ioe) {
232 LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
234 Map<String, Set<String>> classes = detector.getMatchedClasses();
235 if (classes != null && classes.size() > 0) {
236 BundleInfo info = new BundleInfo(bundle, classes);
237 bundleAnnotations.put(bundle.getBundleId(),info);
238 if (LOGGER.isDebugEnabled()) {
239 LOGGER.debug("bindings found in bundle: {}[{}] " +
240 "dependencies {} classes {}", bundle.getSymbolicName(),
241 bundle.getBundleId(),
242 info.getDependencies(bundleAnnotations.values()),
246 // find bundle dependencies
249 public static List<Class<?>> loadClasses(
250 Collection<String> annotatedClasses,
251 Bundle initBundle, Set<String> excludes)
253 List<Class<?>> result = new ArrayList<Class<?>>();
254 StringBuilder errors = new StringBuilder();
255 for (String name : annotatedClasses) {
257 if (excludes != null && excludes.contains(name)) continue;
258 result.add(initBundle.loadClass(name));
259 } catch (ClassNotFoundException e) {
260 errors.append(name).append(", ");
263 if (LOGGER.isDebugEnabled() && errors.length() > 0) {
264 LOGGER.debug("Bundle: {} could not load classes: {}",
265 initBundle.getSymbolicName(), errors.toString());
270 public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
271 if (patterns == null || patterns.length == 0) {
274 StringBuilder regex = new StringBuilder();
275 for (String c : patterns) {
276 if (c.endsWith("*")) {
277 c = c.substring(0, c.length() - 1);
279 if (regex.length() > 0) regex.append("|");
281 if (convert2signature) {
282 regex.append("L").append(c.replaceAll("\\.", "/"));
287 if (LOGGER.isDebugEnabled()) {
288 LOGGER.debug("Merged regex: [{}]", regex.toString());
290 return Pattern.compile(regex.toString());
293 private void validate(List<Class<?>> classes) {
294 if (classes == null || classes.size() == 0) return;
295 Map<String,String> names = new HashMap<String,String>();
296 StringBuilder conflictsMsg = new StringBuilder();
297 for (Class<?> c : classes) {
298 XmlRootElement root = c.getAnnotation(XmlRootElement.class);
299 if (root == null) continue;
300 String rootName = root.name();
301 if ("##default".equals(rootName)) {
302 String clsName = c.getSimpleName();
303 rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1);
305 String other = names.get(rootName);
306 if (other != null && !other.equals(c.getName())) {
307 conflictsMsg.append(System.lineSeparator())
308 .append("[").append(rootName).append(":")
309 .append(c.getName()).append(",").append(other)
312 names.put(rootName, c.getName());
315 if (conflictsMsg.length() > 0) {
316 LOGGER.warn("JAXB type conflicts detected : {}", conflictsMsg.toString());