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.Set;
27  import java.util.StringTokenizer;
28  import java.util.TreeSet;
29  import java.util.concurrent.ConcurrentHashMap;
30  
31  import org.eclipse.jetty.annotations.ClassNameResolver;
32  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
33  import org.eclipse.jetty.util.ConcurrentHashSet;
34  import org.eclipse.jetty.util.resource.Resource;
35  import org.osgi.framework.Bundle;
36  import org.osgi.framework.Constants;
37  
38  /**
39   * 
40   */
41  public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationParser
42  {
43      private Set<URI> _alreadyParsed = new ConcurrentHashSet<URI>();
44      
45      private ConcurrentHashMap<URI,Bundle> _uriToBundle = new ConcurrentHashMap<URI, Bundle>();
46      private ConcurrentHashMap<Bundle,Resource> _bundleToResource = new ConcurrentHashMap<Bundle,Resource>();
47      private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
48      private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
49      
50      
51      /**
52       * Keep track of a jetty URI Resource and its associated OSGi bundle.
53       * @param uri
54       * @param bundle
55       * @throws Exception 
56       */
57      protected Resource indexBundle(Bundle bundle) throws Exception
58      {
59          File bundleFile = BundleFileLocatorHelper.DEFAULT.getBundleInstallLocation(bundle);
60          Resource resource = Resource.newResource(bundleFile.toURI());
61          URI uri = resource.getURI();
62          _uriToBundle.putIfAbsent(uri,bundle);
63          _bundleToUri.putIfAbsent(bundle,uri);
64          _bundleToResource.putIfAbsent(bundle,resource);
65          _resourceToBundle.putIfAbsent(resource,bundle);
66          return resource;
67      }
68      protected URI getURI(Bundle bundle)
69      {
70          return _bundleToUri.get(bundle);
71      }
72      protected Resource getResource(Bundle bundle)
73      {
74          return _bundleToResource.get(bundle);
75      }
76      protected Bundle getBundle (Resource resource)
77      {
78          return _resourceToBundle.get(resource);
79      }
80      
81      
82      /**
83       * 
84       */
85      @Override
86      public void parse (Set<? extends Handler> handlers, URI[] uris, ClassNameResolver resolver)
87      throws Exception
88      {
89          for (URI uri : uris)
90          {
91              Bundle associatedBundle = _uriToBundle.get(uri);
92              if (associatedBundle == null)
93              {
94                  if (!_alreadyParsed.add(uri))
95                  {
96                      continue;
97                  }
98                  //a jar in WEB-INF/lib or the WEB-INF/classes
99                  //use the behavior of the super class for a standard jar.
100                 super.parse(handlers, new URI[] {uri},resolver);
101             }
102             else
103             {
104                 parse(handlers, associatedBundle,resolver);
105             }
106         }
107     }
108     
109     protected void parse(Set<? extends Handler> handlers, Bundle bundle, ClassNameResolver resolver)
110     throws Exception
111     {
112         URI uri = _bundleToUri.get(bundle);
113         if (!_alreadyParsed.add(uri))
114         {
115             return;
116         }
117         
118         String bundleClasspath = (String)bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
119         if (bundleClasspath == null)
120         {
121             bundleClasspath = ".";
122         }
123         //order the paths first by the number of tokens in the path second alphabetically.
124         TreeSet<String> paths = new TreeSet<String>(
125                 new Comparator<String>()
126                 {
127                     public int compare(String o1, String o2)
128                     {
129                         int paths1 = new StringTokenizer(o1,"/",false).countTokens();
130                         int paths2 = new StringTokenizer(o2,"/",false).countTokens();
131                         if (paths1 == paths2)
132                         {
133                             return o1.compareTo(o2);
134                         }
135                         return paths2 - paths1;
136                     }
137                 });
138         boolean hasDotPath = false;
139         StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, ",;", false);
140         while (tokenizer.hasMoreTokens())
141         {
142             String token = tokenizer.nextToken().trim();
143             if (!token.startsWith("/"))
144             {
145                 token = "/" + token;
146             }
147             if (token.equals("/."))
148             {
149                 hasDotPath = true;
150             }
151             else if (!token.endsWith(".jar") && !token.endsWith("/"))
152             {
153                 paths.add(token+"/");
154             }
155             else
156             {
157                 paths.add(token);
158             }
159         }
160         //support the development environment: maybe the classes are inside bin or target/classes
161         //this is certainly not useful in production.
162         //however it makes our life so much easier during development.
163         if (bundle.getEntry("/.classpath") != null)
164         {
165             if (bundle.getEntry("/bin/") != null)
166             {
167                 paths.add("/bin/");
168             }
169             else if (bundle.getEntry("/target/classes/") != null)
170             {
171                 paths.add("/target/classes/");
172             }
173         }
174         Enumeration classes = bundle.findEntries("/","*.class",true);
175         if (classes == null)
176         {
177             return;
178         }
179         while (classes.hasMoreElements())
180         {
181             URL classUrl = (URL) classes.nextElement();
182             String path = classUrl.getPath();
183             //remove the longest path possible:
184             String name = null;
185             for (String prefixPath : paths)
186             {
187                 if (path.startsWith(prefixPath))
188                 {
189                     name = path.substring(prefixPath.length());
190                     break;
191                 }
192             }
193             if (name == null && hasDotPath)
194             {
195                 //remove the starting '/'
196                 name = path.substring(1);
197             }
198             //transform into a classname to pass to the resolver
199             String shortName =  name.replace('/', '.').substring(0,name.length()-6);
200             if ((resolver == null)|| (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
201                 scanClass(handlers, getResource(bundle), classUrl.openStream());
202         }
203     }
204 }