Fix checkstyle if-statements must use braces bundlescanner
[controller.git] / opendaylight / adsal / 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 javax.xml.bind.annotation.XmlRootElement;
26
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;
39
40 /**
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.
44  */
45 /*package*/ class BundleScanner implements SynchronousBundleListener {
46     private static final Logger LOGGER = LoggerFactory.getLogger(BundleScanner.class);
47     private static BundleScanner INSTANCE; // singleton
48
49     private final Pattern annotationPattern;
50     private final Map<Long,BundleInfo> bundleAnnotations =
51             new HashMap<Long, BundleInfo>();
52
53     public static synchronized BundleScanner getInstance() {
54         if (INSTANCE == null) {
55             INSTANCE = new BundleScanner();
56         }
57         return INSTANCE;
58     }
59
60     /*package*/ BundleScanner(Bundle[] bundles) {
61         annotationPattern = mergePatterns(IBundleScanService.ANNOTATIONS_TO_SCAN, true);
62         init(bundles);
63     }
64
65     /*package*/ BundleScanner() {
66         this(FrameworkUtil.getBundle(BundleScanner.class).getBundleContext().getBundles());
67     }
68
69     public List<Class<?>> getAnnotatedClasses(BundleContext context,
70             String[] annotations,
71             Set<String> excludes,
72             boolean includeDependentBundleClasses)
73     {
74         BundleInfo info = bundleAnnotations.get(context.getBundle().getBundleId());
75         if (info == null) {
76             return Collections.emptyList();
77         }
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
84             // initiating bundle
85             Collections.reverse(result);
86             // validate for conflicts only when searching dependencies
87             validate(result);
88         } else {
89             result = info.getAnnotatedClasses(pattern, excludes);
90         }
91         LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
92         return result;
93     }
94
95     ////////////////////////////////////////////////////////////////
96     // SynchronousBundleListener implementation
97     ////////////////////////////////////////////////////////////////
98
99     @Override
100     public void bundleChanged(BundleEvent event) {
101         Bundle bundle = event.getBundle();
102         long id = bundle.getBundleId();
103         switch(event.getType()) {
104             case BundleEvent.RESOLVED :
105                 scan(bundle);
106                 return;
107             case BundleEvent.UNRESOLVED :
108             case BundleEvent.UNINSTALLED :
109                 bundleAnnotations.remove(id);
110                 return;
111         }
112     }
113
114
115     ////////////////////////////////////////////////////////////////
116     //  ClassVisitor implementation
117     ////////////////////////////////////////////////////////////////
118
119     private static class AnnotationDetector extends ClassVisitor {
120         private final Map<String, Set<String>> matchedClasses =
121                 new HashMap<String, Set<String>>();
122
123         private final Pattern annotationsPattern;
124         private Set<String> annotations;
125         private String className;
126         private boolean accessible;
127         private boolean matchedAnnotation;
128
129         public AnnotationDetector(Pattern pattern) {
130             super(Opcodes.ASM4);
131             this.annotationsPattern = pattern;
132         }
133
134         public Map<String, Set<String>> getMatchedClasses() {
135             return new HashMap<String, Set<String>>(matchedClasses);
136         }
137
138         @Override
139         public void visit(int version, int access, String name, String signature,
140                 String superName, String[] interfaces)
141         {
142             //LOGGER.debug("Visiting class:" + name);
143             className = name;
144             accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
145             matchedAnnotation = false;
146             annotations = new HashSet<String>();
147         }
148
149         @Override
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());
156             }
157             return null;
158         }
159
160         @Override
161         public void visitEnd() {
162             if (matchedAnnotation && accessible) {
163                 className = path2class(className);
164                 matchedClasses.put(className, new HashSet<String>(annotations));
165             }
166         }
167     }
168
169     ////////////////////////////////////////////////////////////////
170     // Helpers
171     ////////////////////////////////////////////////////////////////
172
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)
179             {
180                 scan(bundle);
181             }
182         }
183     }
184
185     private static String path2class(String path) {
186         return path.replace(".class", "").replaceAll("/", ".");
187     }
188
189     private static String class2path(String clz) {
190         return clz.replaceAll("\\.", "/");
191     }
192
193     @SuppressWarnings("unused")
194     private static String class2signature(String clz) {
195         return "L" + class2path(clz) + ";";
196     }
197
198     private static String signature2class(String sig) {
199         if (sig.startsWith("L") && sig.endsWith(";")) {
200             sig = sig.substring(1, sig.length()-1);
201         }
202         return path2class(sig);
203     }
204
205    private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
206         List<URL> result = new ArrayList<URL>();
207         boolean recurse = false;
208         if (pkgs == null) {
209             recurse = true;
210             pkgs = new String[] { "/" } ;
211         }
212         for (String pkg : pkgs) {
213             pkg = class2path(pkg);
214             final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
215             if (e != null) {
216                 while (e.hasMoreElements()) {
217                     URL url = e.nextElement();
218                     result.add(url);
219                 }
220             }
221         }
222         return result;
223     }
224
225     private synchronized void scan(Bundle bundle) {
226         AnnotationDetector detector = new AnnotationDetector(annotationPattern);
227         try {
228             for (URL u : getBundleClasses(bundle, null)) {
229                 InputStream is = u.openStream();
230                 new ClassReader(is).accept(detector, 0);
231                 is.close();
232             }
233         } catch (IOException ioe) {
234             LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
235         }
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()),
245                         classes);
246             }
247         }
248         // find bundle dependencies
249     }
250
251     public static List<Class<?>> loadClasses(
252             Collection<String> annotatedClasses,
253             Bundle initBundle, Set<String> excludes)
254     {
255         List<Class<?>> result = new ArrayList<Class<?>>();
256         StringBuilder errors = new StringBuilder();
257         for (String name : annotatedClasses) {
258             try {
259                 if (excludes != null && excludes.contains(name)) {
260                     continue;
261                 }
262                 result.add(initBundle.loadClass(name));
263             } catch (ClassNotFoundException e) {
264                 errors.append(name).append(", ");
265             }
266         }
267         if (LOGGER.isDebugEnabled() && errors.length() > 0) {
268             LOGGER.debug("Bundle: {} could not load classes: {}",
269                     initBundle.getSymbolicName(), errors.toString());
270         }
271         return result;
272     }
273
274     public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
275         if (patterns == null || patterns.length == 0) {
276             return null;
277         }
278         StringBuilder regex = new StringBuilder();
279         for (String c : patterns) {
280             if (c.endsWith("*")) {
281                 c = c.substring(0, c.length() - 1);
282             }
283             if (regex.length() > 0) {
284                 regex.append("|");
285             }
286             regex.append("^");
287             if (convert2signature) {
288                 regex.append("L").append(c.replaceAll("\\.", "/"));
289             } else {
290                 regex.append(c);
291             }
292         }
293         if (LOGGER.isDebugEnabled()) {
294             LOGGER.debug("Merged regex: [{}]", regex.toString());
295         }
296         return Pattern.compile(regex.toString());
297     }
298
299     private void validate(List<Class<?>> classes) {
300         if (classes == null || classes.size() == 0) {
301             return;
302         }
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);
307             if (root == null) {
308                 continue;
309             }
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);
314             }
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)
320                     .append("]");
321             } else {
322                 names.put(rootName, c.getName());
323             }
324         }
325         if (conflictsMsg.length() > 0) {
326             LOGGER.warn("JAXB type conflicts detected : {}", conflictsMsg.toString());
327         }
328     }
329
330 }