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());
76 return Collections.emptyList();
78 Pattern pattern = mergePatterns(annotations, false);
79 List<Class<?>> result = null;
80 if (includeDependentBundleClasses) {
81 result = info.getAnnotatedClasses(bundleAnnotations.values(),
82 pattern, context.getBundle(), excludes);
83 // reverse the list to give precedence to the types loaded from the
85 Collections.reverse(result);
86 // validate for conflicts only when searching dependencies
89 result = info.getAnnotatedClasses(pattern, excludes);
91 LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
95 ////////////////////////////////////////////////////////////////
96 // SynchronousBundleListener implementation
97 ////////////////////////////////////////////////////////////////
100 public void bundleChanged(BundleEvent event) {
101 Bundle bundle = event.getBundle();
102 long id = bundle.getBundleId();
103 switch(event.getType()) {
104 case BundleEvent.RESOLVED :
107 case BundleEvent.UNRESOLVED :
108 case BundleEvent.UNINSTALLED :
109 bundleAnnotations.remove(id);
115 ////////////////////////////////////////////////////////////////
116 // ClassVisitor implementation
117 ////////////////////////////////////////////////////////////////
119 private static class AnnotationDetector extends ClassVisitor {
120 private final Map<String, Set<String>> matchedClasses =
121 new HashMap<String, Set<String>>();
123 private final Pattern annotationsPattern;
124 private Set<String> annotations;
125 private String className;
126 private boolean accessible;
127 private boolean matchedAnnotation;
129 public AnnotationDetector(Pattern pattern) {
131 this.annotationsPattern = pattern;
134 public Map<String, Set<String>> getMatchedClasses() {
135 return new HashMap<String, Set<String>>(matchedClasses);
139 public void visit(int version, int access, String name, String signature,
140 String superName, String[] interfaces)
142 //LOGGER.debug("Visiting class:" + name);
144 accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
145 matchedAnnotation = false;
146 annotations = new HashSet<String>();
150 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
151 //LOGGER.debug("Visiting annotation:" + desc);
152 annotations.add(signature2class(desc));
153 if (!matchedAnnotation) {
154 matchedAnnotation = (annotationsPattern == null ||
155 annotationsPattern.matcher(desc).find());
161 public void visitEnd() {
162 if (matchedAnnotation && accessible) {
163 className = path2class(className);
164 matchedClasses.put(className, new HashSet<String>(annotations));
169 ////////////////////////////////////////////////////////////////
171 ////////////////////////////////////////////////////////////////
173 private synchronized void init(Bundle[] bundles) {
174 for (Bundle bundle : bundles) {
175 int state = bundle.getState();
176 if (state == Bundle.RESOLVED ||
177 state == Bundle.STARTING ||
178 state == Bundle.ACTIVE)
185 private static String path2class(String path) {
186 return path.replace(".class", "").replaceAll("/", ".");
189 private static String class2path(String clz) {
190 return clz.replaceAll("\\.", "/");
193 @SuppressWarnings("unused")
194 private static String class2signature(String clz) {
195 return "L" + class2path(clz) + ";";
198 private static String signature2class(String sig) {
199 if (sig.startsWith("L") && sig.endsWith(";")) {
200 sig = sig.substring(1, sig.length()-1);
202 return path2class(sig);
205 private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
206 List<URL> result = new ArrayList<URL>();
207 boolean recurse = false;
210 pkgs = new String[] { "/" } ;
212 for (String pkg : pkgs) {
213 pkg = class2path(pkg);
214 final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
216 while (e.hasMoreElements()) {
217 URL url = e.nextElement();
225 private synchronized void scan(Bundle bundle) {
226 AnnotationDetector detector = new AnnotationDetector(annotationPattern);
228 for (URL u : getBundleClasses(bundle, null)) {
229 InputStream is = u.openStream();
230 new ClassReader(is).accept(detector, 0);
233 } catch (IOException ioe) {
234 LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
236 Map<String, Set<String>> classes = detector.getMatchedClasses();
237 if (classes != null && classes.size() > 0) {
238 BundleInfo info = new BundleInfo(bundle, classes);
239 bundleAnnotations.put(bundle.getBundleId(),info);
240 if (LOGGER.isDebugEnabled()) {
241 LOGGER.debug("bindings found in bundle: {}[{}] " +
242 "dependencies {} classes {}", bundle.getSymbolicName(),
243 bundle.getBundleId(),
244 info.getDependencies(bundleAnnotations.values()),
248 // find bundle dependencies
251 public static List<Class<?>> loadClasses(
252 Collection<String> annotatedClasses,
253 Bundle initBundle, Set<String> excludes)
255 List<Class<?>> result = new ArrayList<Class<?>>();
256 StringBuilder errors = new StringBuilder();
257 for (String name : annotatedClasses) {
259 if (excludes != null && excludes.contains(name)) {
262 result.add(initBundle.loadClass(name));
263 } catch (ClassNotFoundException e) {
264 errors.append(name).append(", ");
267 if (LOGGER.isDebugEnabled() && errors.length() > 0) {
268 LOGGER.debug("Bundle: {} could not load classes: {}",
269 initBundle.getSymbolicName(), errors.toString());
274 public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
275 if (patterns == null || patterns.length == 0) {
278 StringBuilder regex = new StringBuilder();
279 for (String c : patterns) {
280 if (c.endsWith("*")) {
281 c = c.substring(0, c.length() - 1);
283 if (regex.length() > 0) {
287 if (convert2signature) {
288 regex.append("L").append(c.replaceAll("\\.", "/"));
293 if (LOGGER.isDebugEnabled()) {
294 LOGGER.debug("Merged regex: [{}]", regex.toString());
296 return Pattern.compile(regex.toString());
299 private void validate(List<Class<?>> classes) {
300 if (classes == null || classes.size() == 0) {
303 Map<String,String> names = new HashMap<String,String>();
304 StringBuilder conflictsMsg = new StringBuilder();
305 for (Class<?> c : classes) {
306 XmlRootElement root = c.getAnnotation(XmlRootElement.class);
310 String rootName = root.name();
311 if ("##default".equals(rootName)) {
312 String clsName = c.getSimpleName();
313 rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1);
315 String other = names.get(rootName);
316 if (other != null && !other.equals(c.getName())) {
317 conflictsMsg.append(System.lineSeparator())
318 .append("[").append(rootName).append(":")
319 .append(c.getName()).append(",").append(other)
322 names.put(rootName, c.getName());
325 if (conflictsMsg.length() > 0) {
326 LOGGER.warn("JAXB type conflicts detected : {}", conflictsMsg.toString());