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(), pattern);
78 result = info.getAnnotatedClasses(pattern);
80 LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
84 ////////////////////////////////////////////////////////////////
85 // SynchronousBundleListener implementation
86 ////////////////////////////////////////////////////////////////
89 public void bundleChanged(BundleEvent event) {
90 Bundle bundle = event.getBundle();
91 long id = bundle.getBundleId();
92 switch(event.getType()) {
93 case BundleEvent.RESOLVED :
96 case BundleEvent.UNRESOLVED :
97 case BundleEvent.UNINSTALLED :
98 bundleAnnotations.remove(id);
104 ////////////////////////////////////////////////////////////////
105 // ClassVisitor implementation
106 ////////////////////////////////////////////////////////////////
108 private static class AnnotationDetector extends ClassVisitor {
109 private final Map<String, Set<String>> matchedClasses =
110 new HashMap<String, Set<String>>();
112 private final Pattern annotationsPattern;
113 private Set<String> annotations;
114 private String className;
115 private boolean accessible;
116 private boolean matchedAnnotation;
118 public AnnotationDetector(Pattern pattern) {
120 this.annotationsPattern = pattern;
123 public Map<String, Set<String>> getMatchedClasses() {
124 return new HashMap<String, Set<String>>(matchedClasses);
128 public void visit(int version, int access, String name, String signature,
129 String superName, String[] interfaces)
131 //LOGGER.debug("Visiting class:" + name);
133 accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
134 matchedAnnotation = false;
135 annotations = new HashSet<String>();
139 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
140 //LOGGER.debug("Visiting annotation:" + desc);
141 annotations.add(signature2class(desc));
142 if (!matchedAnnotation) {
143 matchedAnnotation = (annotationsPattern == null ||
144 annotationsPattern.matcher(desc).find());
150 public void visitEnd() {
151 if (matchedAnnotation && accessible) {
152 className = path2class(className);
153 matchedClasses.put(className, new HashSet<String>(annotations));
158 ////////////////////////////////////////////////////////////////
160 ////////////////////////////////////////////////////////////////
162 private synchronized void init(Bundle[] bundles) {
163 for (Bundle bundle : bundles) {
164 int state = bundle.getState();
165 if (state == Bundle.RESOLVED ||
166 state == Bundle.STARTING ||
167 state == Bundle.ACTIVE)
174 private static String path2class(String path) {
175 return path.replace(".class", "").replaceAll("/", ".");
178 private static String class2path(String clz) {
179 return clz.replaceAll("\\.", "/");
182 @SuppressWarnings("unused")
183 private static String class2signature(String clz) {
184 return "L" + class2path(clz) + ";";
187 private static String signature2class(String sig) {
188 if (sig.startsWith("L") && sig.endsWith(";")) {
189 sig = sig.substring(1, sig.length()-1);
191 return path2class(sig);
194 private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
195 List<URL> result = new ArrayList<URL>();
196 boolean recurse = false;
199 pkgs = new String[] { "/" } ;
201 for (String pkg : pkgs) {
202 pkg = class2path(pkg);
203 final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
205 while (e.hasMoreElements()) {
206 URL url = e.nextElement();
214 private synchronized void scan(Bundle bundle) {
215 AnnotationDetector detector = new AnnotationDetector(annotationPattern);
217 for (URL u : getBundleClasses(bundle, null)) {
218 InputStream is = u.openStream();
219 new ClassReader(is).accept(detector, 0);
222 } catch (IOException ioe) {
223 LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
225 Map<String, Set<String>> classes = detector.getMatchedClasses();
226 if (classes != null && classes.size() > 0) {
227 BundleInfo info = new BundleInfo(bundle, classes);
228 bundleAnnotations.put(bundle.getBundleId(),info);
229 if (LOGGER.isDebugEnabled()) {
230 LOGGER.debug("bindings found in bundle: {}[{}] " +
231 "dependencies {} classes {}", bundle.getSymbolicName(),
232 bundle.getBundleId(),
233 info.getDependencies(bundleAnnotations.values()),
237 // find bundle dependencies
240 public static List<Class<?>> loadClasses(Bundle bundle,
241 Collection<String> annotatedClasses)
243 List<Class<?>> result = new ArrayList<Class<?>>();
244 for (String name : annotatedClasses) {
246 result.add(bundle.loadClass(name));
247 } catch (Exception e) {
248 LOGGER.error("Unable to load class: {}", name, e);
254 public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
255 if (patterns == null || patterns.length == 0) {
258 StringBuilder regex = new StringBuilder();
259 for (String c : patterns) {
260 if (c.endsWith("*")) {
261 c = c.substring(0, c.length() - 1);
263 if (regex.length() > 0) regex.append("|");
265 if (convert2signature) {
266 regex.append("L").append(c.replaceAll("\\.", "/"));
271 if (LOGGER.isDebugEnabled()) {
272 LOGGER.debug("Merged regex: [{}]", regex.toString());
274 return Pattern.compile(regex.toString());