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