a5a2073a610886d0aed68044ca70f40c44146556
[controller.git] / opendaylight / northbound / bundlescanner / implementation / src / main / java / org / opendaylight / controller / northbound / bundlescanner / internal / BundleScanner.java
1 /**
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.northbound.bundlescanner.internal;
10
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.net.URL;
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;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.regex.Pattern;
24
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;
37
38 /**
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.
42  */
43 /*package*/ class BundleScanner implements SynchronousBundleListener {
44     private static final Logger LOGGER = LoggerFactory.getLogger(BundleScanner.class);
45     private static BundleScanner INSTANCE; // singleton
46
47     private final Pattern annotationPattern;
48     private final Map<Long,BundleInfo> bundleAnnotations =
49             new HashMap<Long, BundleInfo>();
50
51     public static synchronized BundleScanner getInstance() {
52         if (INSTANCE == null) {
53             INSTANCE = new BundleScanner();
54         }
55         return INSTANCE;
56     }
57
58     /*package*/ BundleScanner(Bundle[] bundles) {
59         annotationPattern = mergePatterns(IBundleScanService.ANNOTATIONS_TO_SCAN, true);
60         init(bundles);
61     }
62
63     /*package*/ BundleScanner() {
64         this(FrameworkUtil.getBundle(BundleScanner.class).getBundleContext().getBundles());
65     }
66
67     public List<Class<?>> getAnnotatedClasses(BundleContext context,
68             String[] annotations,
69             boolean includeDependentBundleClasses)
70     {
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());
78         } else {
79             result = info.getAnnotatedClasses(pattern);
80         }
81         LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
82         return result;
83     }
84
85     ////////////////////////////////////////////////////////////////
86     // SynchronousBundleListener implementation
87     ////////////////////////////////////////////////////////////////
88
89     @Override
90     public void bundleChanged(BundleEvent event) {
91         Bundle bundle = event.getBundle();
92         long id = bundle.getBundleId();
93         switch(event.getType()) {
94             case BundleEvent.RESOLVED :
95                 scan(bundle);
96                 return;
97             case BundleEvent.UNRESOLVED :
98             case BundleEvent.UNINSTALLED :
99                 bundleAnnotations.remove(id);
100                 return;
101         }
102     }
103
104
105     ////////////////////////////////////////////////////////////////
106     //  ClassVisitor implementation
107     ////////////////////////////////////////////////////////////////
108
109     private static class AnnotationDetector extends ClassVisitor {
110         private final Map<String, Set<String>> matchedClasses =
111                 new HashMap<String, Set<String>>();
112
113         private final Pattern annotationsPattern;
114         private Set<String> annotations;
115         private String className;
116         private boolean accessible;
117         private boolean matchedAnnotation;
118
119         public AnnotationDetector(Pattern pattern) {
120             super(Opcodes.ASM4);
121             this.annotationsPattern = pattern;
122         }
123
124         public Map<String, Set<String>> getMatchedClasses() {
125             return new HashMap<String, Set<String>>(matchedClasses);
126         }
127
128         @Override
129         public void visit(int version, int access, String name, String signature,
130                 String superName, String[] interfaces)
131         {
132             //LOGGER.debug("Visiting class:" + name);
133             className = name;
134             accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
135             matchedAnnotation = false;
136             annotations = new HashSet<String>();
137         }
138
139         @Override
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());
146             }
147             return null;
148         }
149
150         @Override
151         public void visitEnd() {
152             if (matchedAnnotation && accessible) {
153                 className = path2class(className);
154                 matchedClasses.put(className, new HashSet<String>(annotations));
155             }
156         }
157     }
158
159     ////////////////////////////////////////////////////////////////
160     // Helpers
161     ////////////////////////////////////////////////////////////////
162
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)
169             {
170                 scan(bundle);
171             }
172         }
173     }
174
175     private static String path2class(String path) {
176         return path.replace(".class", "").replaceAll("/", ".");
177     }
178
179     private static String class2path(String clz) {
180         return clz.replaceAll("\\.", "/");
181     }
182
183     @SuppressWarnings("unused")
184     private static String class2signature(String clz) {
185         return "L" + class2path(clz) + ";";
186     }
187
188     private static String signature2class(String sig) {
189         if (sig.startsWith("L") && sig.endsWith(";")) {
190             sig = sig.substring(1, sig.length()-1);
191         }
192         return path2class(sig);
193     }
194
195    private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
196         List<URL> result = new ArrayList<URL>();
197         boolean recurse = false;
198         if (pkgs == null) {
199             recurse = true;
200             pkgs = new String[] { "/" } ;
201         }
202         for (String pkg : pkgs) {
203             pkg = class2path(pkg);
204             final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
205             if (e != null) {
206                 while (e.hasMoreElements()) {
207                     URL url = e.nextElement();
208                     result.add(url);
209                 }
210             }
211         }
212         return result;
213     }
214
215     private synchronized void scan(Bundle bundle) {
216         AnnotationDetector detector = new AnnotationDetector(annotationPattern);
217         try {
218             for (URL u : getBundleClasses(bundle, null)) {
219                 InputStream is = u.openStream();
220                 new ClassReader(is).accept(detector, 0);
221                 is.close();
222             }
223         } catch (IOException ioe) {
224             LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
225         }
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()),
235                         classes);
236             }
237         }
238         // find bundle dependencies
239     }
240
241     public static List<Class<?>> loadClasses(
242             Collection<String> annotatedClasses,
243             Bundle initBundle)
244     {
245         List<Class<?>> result = new ArrayList<Class<?>>();
246         StringBuilder errors = new StringBuilder();
247         for (String name : annotatedClasses) {
248             try {
249                 result.add(initBundle.loadClass(name));
250             } catch (ClassNotFoundException e) {
251                 errors.append(name).append(", ");
252             }
253         }
254         if (LOGGER.isDebugEnabled() && errors.length() > 0) {
255             LOGGER.debug("Bundle: {} could not load classes: {}",
256                     initBundle.getSymbolicName(), errors.toString());
257         }
258         return result;
259     }
260
261     public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
262         if (patterns == null || patterns.length == 0) {
263             return null;
264         }
265         StringBuilder regex = new StringBuilder();
266         for (String c : patterns) {
267             if (c.endsWith("*")) {
268                 c = c.substring(0, c.length() - 1);
269             }
270             if (regex.length() > 0) regex.append("|");
271             regex.append("^");
272             if (convert2signature) {
273                 regex.append("L").append(c.replaceAll("\\.", "/"));
274             } else {
275                 regex.append(c);
276             }
277         }
278         if (LOGGER.isDebugEnabled()) {
279             LOGGER.debug("Merged regex: [{}]", regex.toString());
280         }
281         return Pattern.compile(regex.toString());
282     }
283
284 }