Merge "UI support for multiple host per port"
[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(), pattern);
77         } else {
78             result = info.getAnnotatedClasses(pattern);
79         }
80         LOGGER.debug("Annotated classes detected: {} matching: {}", result, pattern);
81         return result;
82     }
83
84     ////////////////////////////////////////////////////////////////
85     // SynchronousBundleListener implementation
86     ////////////////////////////////////////////////////////////////
87
88     @Override
89     public void bundleChanged(BundleEvent event) {
90         Bundle bundle = event.getBundle();
91         long id = bundle.getBundleId();
92         switch(event.getType()) {
93             case BundleEvent.RESOLVED :
94                 scan(bundle);
95                 return;
96             case BundleEvent.UNRESOLVED :
97             case BundleEvent.UNINSTALLED :
98                 bundleAnnotations.remove(id);
99                 return;
100         }
101     }
102
103
104     ////////////////////////////////////////////////////////////////
105     //  ClassVisitor implementation
106     ////////////////////////////////////////////////////////////////
107
108     private static class AnnotationDetector extends ClassVisitor {
109         private final Map<String, Set<String>> matchedClasses =
110                 new HashMap<String, Set<String>>();
111
112         private final Pattern annotationsPattern;
113         private Set<String> annotations;
114         private String className;
115         private boolean accessible;
116         private boolean matchedAnnotation;
117
118         public AnnotationDetector(Pattern pattern) {
119             super(Opcodes.ASM4);
120             this.annotationsPattern = pattern;
121         }
122
123         public Map<String, Set<String>> getMatchedClasses() {
124             return new HashMap<String, Set<String>>(matchedClasses);
125         }
126
127         @Override
128         public void visit(int version, int access, String name, String signature,
129                 String superName, String[] interfaces)
130         {
131             //LOGGER.debug("Visiting class:" + name);
132             className = name;
133             accessible = ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC);
134             matchedAnnotation = false;
135             annotations = new HashSet<String>();
136         }
137
138         @Override
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());
145             }
146             return null;
147         }
148
149         @Override
150         public void visitEnd() {
151             if (matchedAnnotation && accessible) {
152                 className = path2class(className);
153                 matchedClasses.put(className, new HashSet<String>(annotations));
154             }
155         }
156     }
157
158     ////////////////////////////////////////////////////////////////
159     // Helpers
160     ////////////////////////////////////////////////////////////////
161
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)
168             {
169                 scan(bundle);
170             }
171         }
172     }
173
174     private static String path2class(String path) {
175         return path.replace(".class", "").replaceAll("/", ".");
176     }
177
178     private static String class2path(String clz) {
179         return clz.replaceAll("\\.", "/");
180     }
181
182     @SuppressWarnings("unused")
183     private static String class2signature(String clz) {
184         return "L" + class2path(clz) + ";";
185     }
186
187     private static String signature2class(String sig) {
188         if (sig.startsWith("L") && sig.endsWith(";")) {
189             sig = sig.substring(1, sig.length()-1);
190         }
191         return path2class(sig);
192     }
193
194    private static List<URL> getBundleClasses(Bundle bundle, String[] pkgs) {
195         List<URL> result = new ArrayList<URL>();
196         boolean recurse = false;
197         if (pkgs == null) {
198             recurse = true;
199             pkgs = new String[] { "/" } ;
200         }
201         for (String pkg : pkgs) {
202             pkg = class2path(pkg);
203             final Enumeration<URL> e = bundle.findEntries(pkg, "*.class", recurse);
204             if (e != null) {
205                 while (e.hasMoreElements()) {
206                     URL url = e.nextElement();
207                     result.add(url);
208                 }
209             }
210         }
211         return result;
212     }
213
214     private synchronized void scan(Bundle bundle) {
215         AnnotationDetector detector = new AnnotationDetector(annotationPattern);
216         try {
217             for (URL u : getBundleClasses(bundle, null)) {
218                 InputStream is = u.openStream();
219                 new ClassReader(is).accept(detector, 0);
220                 is.close();
221             }
222         } catch (IOException ioe) {
223             LOGGER.error("Error scanning classes in bundle: {}", bundle.getSymbolicName(), ioe);
224         }
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()),
234                         classes);
235             }
236         }
237         // find bundle dependencies
238     }
239
240     public static List<Class<?>> loadClasses(Bundle bundle,
241             Collection<String> annotatedClasses)
242     {
243         List<Class<?>> result = new ArrayList<Class<?>>();
244         for (String name : annotatedClasses) {
245             try {
246                 result.add(bundle.loadClass(name));
247             } catch (Exception e) {
248                 LOGGER.error("Unable to load class: {}", name, e);
249             }
250         }
251         return result;
252     }
253
254     public static Pattern mergePatterns(String[] patterns, boolean convert2signature) {
255         if (patterns == null || patterns.length == 0) {
256             return null;
257         }
258         StringBuilder regex = new StringBuilder();
259         for (String c : patterns) {
260             if (c.endsWith("*")) {
261                 c = c.substring(0, c.length() - 1);
262             }
263             if (regex.length() > 0) regex.append("|");
264             regex.append("^");
265             if (convert2signature) {
266                 regex.append("L").append(c.replaceAll("\\.", "/"));
267             } else {
268                 regex.append(c);
269             }
270         }
271         if (LOGGER.isDebugEnabled()) {
272             LOGGER.debug("Merged regex: [{}]", regex.toString());
273         }
274         return Pattern.compile(regex.toString());
275     }
276
277 }