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 org.objectweb.asm.AnnotationVisitor;
26 import org.objectweb.asm.ClassReader;
27 import org.objectweb.asm.ClassVisitor;
28 import org.objectweb.asm.Opcodes;
29 import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService;
30 import org.osgi.framework.Bundle;
31 import org.osgi.framework.BundleContext;
32 import org.osgi.framework.BundleEvent;
33 import org.osgi.framework.FrameworkUtil;
34 import org.osgi.framework.SynchronousBundleListener;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * The custom bundle scanner scans annotations on bundles and is used for
40 * constructing JAXBContext instances. It listens for bundle events and updates
41 * the metadata in realtime.
43 /*package*/ class BundleScanner implements SynchronousBundleListener {
44 private static final Logger LOGGER = LoggerFactory.getLogger(BundleScanner.class);
45 private static BundleScanner INSTANCE; // singleton
47 private final Pattern annotationPattern;
48 private final Map<Long,BundleInfo> bundleAnnotations =
49 new HashMap<Long, BundleInfo>();
51 public static synchronized BundleScanner getInstance() {
52 if (INSTANCE == null) {
53 INSTANCE = new BundleScanner();
58 /*package*/ BundleScanner(Bundle[] bundles) {
59 annotationPattern = mergePatterns(IBundleScanService.ANNOTATIONS_TO_SCAN, true);
63 /*package*/ BundleScanner() {
64 this(FrameworkUtil.getBundle(BundleScanner.class).getBundleContext().getBundles());
67 public List<Class<?>> getAnnotatedClasses(BundleContext context,
69 boolean includeDependentBundleClasses)
71 BundleInfo info = bundleAnnotations.get(context.getBundle().getBundleId());
72 if (info == null) return Collections.emptyList();
73 Pattern pattern = mergePatterns(annotations, false);
74 List<Class<?>> result = null;
75 if (includeDependentBundleClasses) {
76 result = info.getAnnotatedClasses(bundleAnnotations.values(),
77 pattern, context.getBundle());
79 result = info.getAnnotatedClasses(pattern);
81 LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
85 ////////////////////////////////////////////////////////////////
86 // SynchronousBundleListener implementation
87 ////////////////////////////////////////////////////////////////
90 public void bundleChanged(BundleEvent event) {
91 Bundle bundle = event.getBundle();
92 long id = bundle.getBundleId();
93 switch(event.getType()) {
94 case BundleEvent.RESOLVED :
97 case BundleEvent.UNRESOLVED :
98 case BundleEvent.UNINSTALLED :
99 bundleAnnotations.remove(id);
105 ////////////////////////////////////////////////////////////////
106 // ClassVisitor implementation
107 ////////////////////////////////////////////////////////////////
109 private static class AnnotationDetector extends ClassVisitor {
110 private final Map<String, Set<String>> matchedClasses =
111 new HashMap<String, Set<String>>();
113 private final Pattern annotationsPattern;
114 private Set<String> annotations;
115 private String className;
116 private boolean accessible;
117 private boolean matchedAnnotation;
119 public AnnotationDetector(Pattern pattern) {
121 this.annotationsPattern = pattern;
124 public Map<String, Set<String>> getMatchedClasses() {
125 return new HashMap<String, Set<String>>(matchedClasses);
129 public void visit(int version, int access, String name, String signature,
130 String superName, String[] interfaces)
132 //LOGGER.debug("Visiting class:" + name);
134 accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
135 matchedAnnotation = false;
136 annotations = new HashSet<String>();
140 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
141 //LOGGER.debug("Visiting annotation:" + desc);
142 annotations.add(signature2class(desc));
143 if (!matchedAnnotation) {
144 matchedAnnotation = (annotationsPattern == null ||
145 annotationsPattern.matcher(desc).find());
151 public void visitEnd() {
152 if (matchedAnnotation && accessible) {
153 className = path2class(className);
154 matchedClasses.put(className, new HashSet<String>(annotations));
159 ////////////////////////////////////////////////////////////////
161 ////////////////////////////////////////////////////////////////
163 private synchronized void init(Bundle[] bundles) {
164 for (Bundle bundle : bundles) {
165 int state = bundle.getState();
166 if (state == Bundle.RESOLVED ||
167 state == Bundle.STARTING ||
168 state == Bundle.ACTIVE)
175 private static String path2class(String path) {
176 return path.replace(".class", "").replaceAll("/", ".");
179 private static String class2path(String clz) {
180 return clz.replaceAll("\\.", "/");
183 @SuppressWarnings("unused")
184 private static String class2signature(String clz) {
185 return "L" + class2path(clz) + ";";
188 private static String signature2class(String sig) {
189 if (sig.startsWith("L") && sig.endsWith(";")) {
190 sig = sig.substring(1, sig.length()-1);
192 return path2class(sig);
195 private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
196 List<URL> result = new ArrayList<URL>();
197 boolean recurse = false;
200 pkgs = new String[] { "/" } ;
202 for (String pkg : pkgs) {
203 pkg = class2path(pkg);
204 final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
206 while (e.hasMoreElements()) {
207 URL url = e.nextElement();
215 private synchronized void scan(Bundle bundle) {
216 AnnotationDetector detector = new AnnotationDetector(annotationPattern);
218 for (URL u : getBundleClasses(bundle, null)) {
219 InputStream is = u.openStream();
220 new ClassReader(is).accept(detector, 0);
223 } catch (IOException ioe) {
224 LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
226 Map<String, Set<String>> classes = detector.getMatchedClasses();
227 if (classes != null && classes.size() > 0) {
228 BundleInfo info = new BundleInfo(bundle, classes);
229 bundleAnnotations.put(bundle.getBundleId(),info);
230 if (LOGGER.isDebugEnabled()) {
231 LOGGER.debug("bindings found in bundle: {}[{}] " +
232 "dependencies {} classes {}", bundle.getSymbolicName(),
233 bundle.getBundleId(),
234 info.getDependencies(bundleAnnotations.values()),
238 // find bundle dependencies
241 public static List<Class<?>> loadClasses(
242 Collection<String> annotatedClasses,
245 List<Class<?>> result = new ArrayList<Class<?>>();
246 StringBuilder errors = new StringBuilder();
247 for (String name : annotatedClasses) {
249 result.add(initBundle.loadClass(name));
250 } catch (ClassNotFoundException e) {
251 errors.append(name).append(", ");
254 if (LOGGER.isDebugEnabled() && errors.length() > 0) {
255 LOGGER.debug("Bundle: {} could not load classes: {}",
256 initBundle.getSymbolicName(), errors.toString());
261 public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
262 if (patterns == null || patterns.length == 0) {
265 StringBuilder regex = new StringBuilder();
266 for (String c : patterns) {
267 if (c.endsWith("*")) {
268 c = c.substring(0, c.length() - 1);
270 if (regex.length() > 0) regex.append("|");
272 if (convert2signature) {
273 regex.append("L").append(c.replaceAll("\\.", "/"));
278 if (LOGGER.isDebugEnabled()) {
279 LOGGER.debug("Merged regex: [{}]", regex.toString());
281 return Pattern.compile(regex.toString());