blob: a0aac731978ab1dace27bbd06a299cb20c4ef209 [file] [log] [blame]
//
// ========================================================================
// 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);
}
}
}
}
}