View Javadoc

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