View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.osgi.annotations;
20  
21  import java.io.File;
22  import java.net.URI;
23  import java.net.URL;
24  import java.util.Comparator;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.StringTokenizer;
31  import java.util.TreeSet;
32  import java.util.concurrent.ConcurrentHashMap;
33  
34  import org.eclipse.jetty.annotations.ClassNameResolver;
35  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
36  import org.eclipse.jetty.util.ConcurrentHashSet;
37  import org.eclipse.jetty.util.resource.Resource;
38  import org.objectweb.asm.Opcodes;
39  import org.osgi.framework.Bundle;
40  import org.osgi.framework.Constants;
41  
42  /**
43   * 
44   */
45  public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationParser
46  {
47      private Set<URI> _alreadyParsed = new ConcurrentHashSet<URI>();
48      
49      private ConcurrentHashMap<URI,Bundle> _uriToBundle = new ConcurrentHashMap<URI, Bundle>();
50      private ConcurrentHashMap<Bundle,Resource> _bundleToResource = new ConcurrentHashMap<Bundle,Resource>();
51      private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
52      private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
53      
54      static
55      {
56          //As of jetty 9.2.0, the impl of asm visitor classes is compatible with both asm4 and asm5.
57          //We need to use asm4 with osgi, because we need to use aries spifly to support annotations,
58          //and currently this only supports asm4. Therefore, we set the asm api version to be 4 for osgi.
59          ASM_OPCODE_VERSION = Opcodes.ASM4;
60      }
61      
62      /**
63       * Keep track of a jetty URI Resource and its associated OSGi bundle.
64       * @param uri
65       * @param bundle
66       * @throws Exception 
67       */
68      protected Resource indexBundle(Bundle bundle) throws Exception
69      {
70          File bundleFile = BundleFileLocatorHelper.DEFAULT.getBundleInstallLocation(bundle);
71          Resource resource = Resource.newResource(bundleFile.toURI());
72          URI uri = resource.getURI();
73          _uriToBundle.putIfAbsent(uri,bundle);
74          _bundleToUri.putIfAbsent(bundle,uri);
75          _bundleToResource.putIfAbsent(bundle,resource);
76          _resourceToBundle.putIfAbsent(resource,bundle);
77          return resource;
78      }
79      protected URI getURI(Bundle bundle)
80      {
81          return _bundleToUri.get(bundle);
82      }
83      protected Resource getResource(Bundle bundle)
84      {
85          return _bundleToResource.get(bundle);
86      }
87      protected Bundle getBundle (Resource resource)
88      {
89          return _resourceToBundle.get(resource);
90      }
91      
92      
93      /**
94       * 
95       */
96      @Override
97      public void parse (Set<? extends Handler> handlers, URI[] uris, ClassNameResolver resolver)
98      throws Exception
99      {
100         for (URI uri : uris)
101         {
102             Bundle associatedBundle = _uriToBundle.get(uri);
103             if (associatedBundle == null)
104             {
105                 if (!_alreadyParsed.add(uri))
106                 {
107                     continue;
108                 }
109                 //a jar in WEB-INF/lib or the WEB-INF/classes
110                 //use the behavior of the super class for a standard jar.
111                 super.parse(handlers, new URI[] {uri},resolver);
112             }
113             else
114             {
115                 parse(handlers, associatedBundle,resolver);
116             }
117         }
118     }
119     
120     protected void parse(Set<? extends Handler> handlers, Bundle bundle, ClassNameResolver resolver)
121     throws Exception
122     {
123         URI uri = _bundleToUri.get(bundle);
124         if (!_alreadyParsed.add(uri))
125         {
126             return;
127         }
128         
129         String bundleClasspath = (String)bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
130         if (bundleClasspath == null)
131         {
132             bundleClasspath = ".";
133         }
134         //order the paths first by the number of tokens in the path second alphabetically.
135         TreeSet<String> paths = new TreeSet<String>(
136                 new Comparator<String>()
137                 {
138                     public int compare(String o1, String o2)
139                     {
140                         int paths1 = new StringTokenizer(o1,"/",false).countTokens();
141                         int paths2 = new StringTokenizer(o2,"/",false).countTokens();
142                         if (paths1 == paths2)
143                         {
144                             return o1.compareTo(o2);
145                         }
146                         return paths2 - paths1;
147                     }
148                 });
149         boolean hasDotPath = false;
150         StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, ",;", false);
151         while (tokenizer.hasMoreTokens())
152         {
153             String token = tokenizer.nextToken().trim();
154             if (!token.startsWith("/"))
155             {
156                 token = "/" + token;
157             }
158             if (token.equals("/."))
159             {
160                 hasDotPath = true;
161             }
162             else if (!token.endsWith(".jar") && !token.endsWith("/"))
163             {
164                 paths.add(token+"/");
165             }
166             else
167             {
168                 paths.add(token);
169             }
170         }
171         //support the development environment: maybe the classes are inside bin or target/classes
172         //this is certainly not useful in production.
173         //however it makes our life so much easier during development.
174         if (bundle.getEntry("/.classpath") != null)
175         {
176             if (bundle.getEntry("/bin/") != null)
177             {
178                 paths.add("/bin/");
179             }
180             else if (bundle.getEntry("/target/classes/") != null)
181             {
182                 paths.add("/target/classes/");
183             }
184         }
185         Enumeration classes = bundle.findEntries("/","*.class",true);
186         if (classes == null)
187         {
188             return;
189         }
190         while (classes.hasMoreElements())
191         {
192             URL classUrl = (URL) classes.nextElement();
193             String path = classUrl.getPath();
194             //remove the longest path possible:
195             String name = null;
196             for (String prefixPath : paths)
197             {
198                 if (path.startsWith(prefixPath))
199                 {
200                     name = path.substring(prefixPath.length());
201                     break;
202                 }
203             }
204             if (name == null && hasDotPath)
205             {
206                 //remove the starting '/'
207                 name = path.substring(1);
208             }
209             //transform into a classname to pass to the resolver
210             String shortName =  name.replace('/', '.').substring(0,name.length()-6);
211             if ((resolver == null)|| (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
212                 scanClass(handlers, getResource(bundle), classUrl.openStream());
213         }
214     }
215     
216 }