| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.osgi.annotations; |
| |
| import java.io.File; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.Comparator; |
| import java.util.Enumeration; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.eclipse.jetty.annotations.ClassNameResolver; |
| import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; |
| import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory; |
| import org.eclipse.jetty.util.ConcurrentHashSet; |
| import org.eclipse.jetty.util.resource.Resource; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.Constants; |
| |
| /** |
| * |
| */ |
| public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationParser |
| { |
| private Set<URI> _alreadyParsed = new ConcurrentHashSet<URI>(); |
| |
| private ConcurrentHashMap<URI,Bundle> _uriToBundle = new ConcurrentHashMap<URI, Bundle>(); |
| private ConcurrentHashMap<Bundle,Resource> _bundleToResource = new ConcurrentHashMap<Bundle,Resource>(); |
| private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>(); |
| private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>(); |
| |
| |
| /** |
| * Keep track of a jetty URI Resource and its associated OSGi bundle. |
| * @param uri |
| * @param bundle |
| * @throws Exception |
| */ |
| protected Resource indexBundle(Bundle bundle) throws Exception |
| { |
| File bundleFile = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle); |
| Resource resource = Resource.newResource(bundleFile.toURI()); |
| URI uri = resource.getURI(); |
| _uriToBundle.putIfAbsent(uri,bundle); |
| _bundleToUri.putIfAbsent(bundle,uri); |
| _bundleToResource.putIfAbsent(bundle,resource); |
| _resourceToBundle.putIfAbsent(resource,bundle); |
| return resource; |
| } |
| protected URI getURI(Bundle bundle) |
| { |
| return _bundleToUri.get(bundle); |
| } |
| protected Resource getResource(Bundle bundle) |
| { |
| return _bundleToResource.get(bundle); |
| } |
| protected Bundle getBundle (Resource resource) |
| { |
| return _resourceToBundle.get(resource); |
| } |
| |
| |
| /** |
| * |
| */ |
| @Override |
| public void parse (Set<? extends Handler> handlers, URI[] uris, ClassNameResolver resolver) |
| throws Exception |
| { |
| for (URI uri : uris) |
| { |
| Bundle associatedBundle = _uriToBundle.get(uri); |
| if (associatedBundle == null) |
| { |
| if (!_alreadyParsed.add(uri)) |
| { |
| continue; |
| } |
| //a jar in WEB-INF/lib or the WEB-INF/classes |
| //use the behavior of the super class for a standard jar. |
| super.parse(handlers, new URI[] {uri},resolver); |
| } |
| else |
| { |
| parse(handlers, associatedBundle,resolver); |
| } |
| } |
| } |
| |
| protected void parse(Set<? extends Handler> handlers, Bundle bundle, ClassNameResolver resolver) |
| throws Exception |
| { |
| URI uri = _bundleToUri.get(bundle); |
| if (!_alreadyParsed.add(uri)) |
| { |
| return; |
| } |
| |
| String bundleClasspath = (String)bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH); |
| if (bundleClasspath == null) |
| { |
| bundleClasspath = "."; |
| } |
| //order the paths first by the number of tokens in the path second alphabetically. |
| TreeSet<String> paths = new TreeSet<String>( |
| new Comparator<String>() |
| { |
| public int compare(String o1, String o2) |
| { |
| int paths1 = new StringTokenizer(o1,"/",false).countTokens(); |
| int paths2 = new StringTokenizer(o2,"/",false).countTokens(); |
| if (paths1 == paths2) |
| { |
| return o1.compareTo(o2); |
| } |
| return paths2 - paths1; |
| } |
| }); |
| boolean hasDotPath = false; |
| StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, ",;", false); |
| while (tokenizer.hasMoreTokens()) |
| { |
| String token = tokenizer.nextToken().trim(); |
| if (!token.startsWith("/")) |
| { |
| token = "/" + token; |
| } |
| if (token.equals("/.")) |
| { |
| hasDotPath = true; |
| } |
| else if (!token.endsWith(".jar") && !token.endsWith("/")) |
| { |
| paths.add(token+"/"); |
| } |
| else |
| { |
| paths.add(token); |
| } |
| } |
| //support the development environment: maybe the classes are inside bin or target/classes |
| //this is certainly not useful in production. |
| //however it makes our life so much easier during development. |
| if (bundle.getEntry("/.classpath") != null) |
| { |
| if (bundle.getEntry("/bin/") != null) |
| { |
| paths.add("/bin/"); |
| } |
| else if (bundle.getEntry("/target/classes/") != null) |
| { |
| paths.add("/target/classes/"); |
| } |
| } |
| Enumeration classes = bundle.findEntries("/","*.class",true); |
| if (classes == null) |
| { |
| return; |
| } |
| while (classes.hasMoreElements()) |
| { |
| URL classUrl = (URL) classes.nextElement(); |
| String path = classUrl.getPath(); |
| //remove the longest path possible: |
| String name = null; |
| for (String prefixPath : paths) |
| { |
| if (path.startsWith(prefixPath)) |
| { |
| name = path.substring(prefixPath.length()); |
| break; |
| } |
| } |
| if (name == null && hasDotPath) |
| { |
| //remove the starting '/' |
| name = path.substring(1); |
| } |
| if (name == null) |
| { |
| //found some .class file in the archive that was not under one of the prefix paths |
| //or the bundle classpath wasn't simply ".", so skip it |
| continue; |
| } |
| //transform into a classname to pass to the resolver |
| String shortName = name.replace('/', '.').substring(0,name.length()-6); |
| if ((resolver == null) || (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName)))) |
| { |
| try (InputStream classInputStream = classUrl.openStream()) |
| { |
| scanClass(handlers, getResource(bundle), classInputStream); |
| } |
| } |
| } |
| } |
| } |