Merge "Bug 1029: Remove dead code: samples/clustersession"
[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) return Collections.emptyList();
76         Pattern pattern = mergePatterns(annotations, false);
77         List<Class<?>> result = null;
78         if (includeDependentBundleClasses) {
79             result = info.getAnnotatedClasses(bundleAnnotations.values(),
80                     pattern, context.getBundle(), excludes);
81             // reverse the list to give precedence to the types loaded from the
82             // initiating bundle
83             Collections.reverse(result);
84             // validate for conflicts only when searching dependencies
85             validate(result);
86         } else {
87             result = info.getAnnotatedClasses(pattern, excludes);
88         }
89         LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
90         return result;
91     }
92
93     ////////////////////////////////////////////////////////////////
94     // SynchronousBundleListener implementation
95     ////////////////////////////////////////////////////////////////
96
97     @Override
98     public void bundleChanged(BundleEvent event) {
99         Bundle bundle = event.getBundle();
100         long id = bundle.getBundleId();
101         switch(event.getType()) {
102             case BundleEvent.RESOLVED :
103                 scan(bundle);
104                 return;
105             case BundleEvent.UNRESOLVED :
106             case BundleEvent.UNINSTALLED :
107                 bundleAnnotations.remove(id);
108                 return;
109         }
110     }
111
112
113     ////////////////////////////////////////////////////////////////
114     //  ClassVisitor implementation
115     ////////////////////////////////////////////////////////////////
116
117     private static class AnnotationDetector extends ClassVisitor {
118         private final Map<String, Set<String>> matchedClasses =
119                 new HashMap<String, Set<String>>();
120
121         private final Pattern annotationsPattern;
122         private Set<String> annotations;
123         private String className;
124         private boolean accessible;
125         private boolean matchedAnnotation;
126
127         public AnnotationDetector(Pattern pattern) {
128             super(Opcodes.ASM4);
129             this.annotationsPattern = pattern;
130         }
131
132         public Map<String, Set<String>> getMatchedClasses() {
133             return new HashMap<String, Set<String>>(matchedClasses);
134         }
135
136         @Override
137         public void visit(int version, int access, String name, String signature,
138                 String superName, String[] interfaces)
139         {
140             //LOGGER.debug("Visiting class:" + name);
141             className = name;
142             accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
143             matchedAnnotation = false;
144             annotations = new HashSet<String>();
145         }
146
147         @Override
148         public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
149             //LOGGER.debug("Visiting annotation:" + desc);
150             annotations.add(signature2class(desc));
151             if (!matchedAnnotation) {
152                 matchedAnnotation = (annotationsPattern == null ||
153                         annotationsPattern.matcher(desc).find());
154             }
155             return null;
156         }
157
158         @Override
159         public void visitEnd() {
160             if (matchedAnnotation && accessible) {
161                 className = path2class(className);
162                 matchedClasses.put(className, new HashSet<String>(annotations));
163             }
164         }
165     }
166
167     ////////////////////////////////////////////////////////////////
168     // Helpers
169     ////////////////////////////////////////////////////////////////
170
171     private synchronized void init(Bundle[] bundles) {
172         for (Bundle bundle : bundles) {
173             int state = bundle.getState();
174             if (state == Bundle.RESOLVED ||
175                 state == Bundle.STARTING ||
176                 state == Bundle.ACTIVE)
177             {
178                 scan(bundle);
179             }
180         }
181     }
182
183     private static String path2class(String path) {
184         return path.replace(".class", "").replaceAll("/", ".");
185     }
186
187     private static String class2path(String clz) {
188         return clz.replaceAll("\\.", "/");
189     }
190
191     @SuppressWarnings("unused")
192     private static String class2signature(String clz) {
193         return "L" + class2path(clz) + ";";
194     }
195
196     private static String signature2class(String sig) {
197         if (sig.startsWith("L") && sig.endsWith(";")) {
198             sig = sig.substring(1, sig.length()-1);
199         }
200         return path2class(sig);
201     }
202
203    private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
204         List<URL> result = new ArrayList<URL>();
205         boolean recurse = false;
206         if (pkgs == null) {
207             recurse = true;
208             pkgs = new String[] { "/" } ;
209         }
210         for (String pkg : pkgs) {
211             pkg = class2path(pkg);
212             final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
213             if (e != null) {
214                 while (e.hasMoreElements()) {
215                     URL url = e.nextElement();
216                     result.add(url);
217                 }
218             }
219         }
220         return result;
221     }
222
223     private synchronized void scan(Bundle bundle) {
224         AnnotationDetector detector = new AnnotationDetector(annotationPattern);
225         try {
226             for (URL u : getBundleClasses(bundle, null)) {
227                 InputStream is = u.openStream();
228                 new ClassReader(is).accept(detector, 0);
229                 is.close();
230             }
231         } catch (IOException ioe) {
232             LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
233         }
234         Map<String, Set<String>> classes = detector.getMatchedClasses();
235         if (classes != null && classes.size() > 0) {
236             BundleInfo info = new BundleInfo(bundle, classes);
237             bundleAnnotations.put(bundle.getBundleId(),info);
238             if (LOGGER.isDebugEnabled()) {
239                 LOGGER.debug("bindings found in bundle: {}[{}] " +
240                         "dependencies {} classes {}", bundle.getSymbolicName(),
241                         bundle.getBundleId(),
242                         info.getDependencies(bundleAnnotations.values()),
243                         classes);
244             }
245         }
246         // find bundle dependencies
247     }
248
249     public static List<Class<?>> loadClasses(
250             Collection<String> annotatedClasses,
251             Bundle initBundle, Set<String> excludes)
252     {
253         List<Class<?>> result = new ArrayList<Class<?>>();
254         StringBuilder errors = new StringBuilder();
255         for (String name : annotatedClasses) {
256             try {
257                 if (excludes != null && excludes.contains(name)) continue;
258                 result.add(initBundle.loadClass(name));
259             } catch (ClassNotFoundException e) {
260                 errors.append(name).append(", ");
261             }
262         }
263         if (LOGGER.isDebugEnabled() && errors.length() > 0) {
264             LOGGER.debug("Bundle: {} could not load classes: {}",
265                     initBundle.getSymbolicName(), errors.toString());
266         }
267         return result;
268     }
269
270     public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
271         if (patterns == null || patterns.length == 0) {
272             return null;
273         }
274         StringBuilder regex = new StringBuilder();
275         for (String c : patterns) {
276             if (c.endsWith("*")) {
277                 c = c.substring(0, c.length() - 1);
278             }
279             if (regex.length() > 0) regex.append("|");
280             regex.append("^");
281             if (convert2signature) {
282                 regex.append("L").append(c.replaceAll("\\.", "/"));
283             } else {
284                 regex.append(c);
285             }
286         }
287         if (LOGGER.isDebugEnabled()) {
288             LOGGER.debug("Merged regex: [{}]", regex.toString());
289         }
290         return Pattern.compile(regex.toString());
291     }
292
293     private void validate(List<Class<?>> classes) {
294         if (classes == null || classes.size() == 0) return;
295         Map<String,String> names = new HashMap<String,String>();
296         StringBuilder conflictsMsg = new StringBuilder();
297         for (Class<?> c : classes) {
298             XmlRootElement root = c.getAnnotation(XmlRootElement.class);
299             if (root == null) continue;
300             String rootName = root.name();
301             if ("##default".equals(rootName)) {
302                 String clsName = c.getSimpleName();
303                 rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1);
304             }
305             String other = names.get(rootName);
306             if (other != null && !other.equals(c.getName())) {
307                 conflictsMsg.append(System.lineSeparator())
308                     .append("[").append(rootName).append(":")
309                     .append(c.getName()).append(",").append(other)
310                     .append("]");
311             } else {
312                 names.put(rootName, c.getName());
313             }
314         }
315         if (conflictsMsg.length() > 0) {
316             LOGGER.warn("JAXB type conflicts detected : {}", conflictsMsg.toString());
317         }
318     }
319
320 }