blob: 870a1f3c1ee33c8f50900d338e8285ecb4cbd9fc [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 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.boot;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.osgi.boot.utils.Util;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* OSGiWebInfConfiguration
*
* Handle adding resources found in bundle fragments, and add them into the
*/
public class OSGiWebInfConfiguration extends WebInfConfiguration
{
private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
/**
* Comma separated list of symbolic names of bundles that contain tlds that should be considered
* as on the container classpath
*/
public static final String SYS_PROP_TLD_BUNDLES = "org.eclipse.jetty.osgi.tldbundles";
/**
* Regex of symbolic names of bundles that should be considered to be on the container classpath
*/
public static final String CONTAINER_BUNDLE_PATTERN = "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern";
public static final String FRAGMENT_AND_REQUIRED_BUNDLES = "org.eclipse.jetty.osgi.fragmentAndRequiredBundles";
public static final String FRAGMENT_AND_REQUIRED_RESOURCES = "org.eclipse.jetty.osgi.fragmentAndRequiredResources";
/* ------------------------------------------------------------ */
/**
* Check to see if there have been any bundle symbolic names added of bundles that should be
* regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
* This can be defined in:
* <ol>
* <li>SystemProperty SYS_PROP_TLD_BUNDLES</li>
* <li>DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN</li>
* </ol>
*
* We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds
* MANIFEST.MF header.
*
* @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
super.preConfigure(context);
//Check to see if there have been any bundle symbolic names added of bundles that should be
//regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
//This can be defined in:
// 1. SystemProperty SYS_PROP_TLD_BUNDLES
// 2. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN
String tmp = (String)context.getAttribute(CONTAINER_BUNDLE_PATTERN);
Pattern pattern = (tmp==null?null:Pattern.compile(tmp));
List<String> names = new ArrayList<String>();
tmp = System.getProperty(SYS_PROP_TLD_BUNDLES);
if (tmp != null)
{
StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
while (tokenizer.hasMoreTokens())
names.add(tokenizer.nextToken());
}
HashSet<Resource> matchingResources = new HashSet<Resource>();
if ( !names.isEmpty() || pattern != null)
{
Bundle[] bundles = FrameworkUtil.getBundle(OSGiWebInfConfiguration.class).getBundleContext().getBundles();
for (Bundle bundle : bundles)
{
LOG.debug("Checking bundle {}:{}", bundle.getBundleId(), bundle.getSymbolicName());
if (pattern != null)
{
// if bundle symbolic name matches the pattern
if (pattern.matcher(bundle.getSymbolicName()).matches())
{
//get the file location of the jar and put it into the list of container jars that will be scanned for stuff (including tlds)
matchingResources.addAll(getBundleAsResource(bundle));
}
}
if (names != null)
{
//if there is an explicit bundle name, then check if it matches
if (names.contains(bundle.getSymbolicName()))
matchingResources.addAll(getBundleAsResource(bundle));
}
}
}
for (Resource r:matchingResources)
{
context.getMetaData().addContainerResource(r);
}
}
@Override
public void postConfigure(WebAppContext context) throws Exception
{
context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, null);
context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, null);
super.postConfigure(context);
}
/* ------------------------------------------------------------ */
/**
* Consider the fragment bundles associated with the bundle of the webapp being deployed.
*
*
* @see org.eclipse.jetty.webapp.WebInfConfiguration#findJars(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
protected List<Resource> findJars (WebAppContext context)
throws Exception
{
List<Resource> mergedResources = new ArrayList<Resource>();
//get jars from WEB-INF/lib if there are any
List<Resource> webInfJars = super.findJars(context);
if (webInfJars != null)
mergedResources.addAll(webInfJars);
//add fragment jars and any Required-Bundles as if in WEB-INF/lib of the associated webapp
Bundle[] bundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
if (bundles != null && bundles.length > 0)
{
Set<Bundle> fragsAndReqsBundles = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
if (fragsAndReqsBundles == null)
{
fragsAndReqsBundles = new HashSet<Bundle>();
context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, fragsAndReqsBundles);
}
Set<Resource> fragsAndReqsResources = (Set<Resource>)context.getAttribute(FRAGMENT_AND_REQUIRED_RESOURCES);
if (fragsAndReqsResources == null)
{
fragsAndReqsResources = new HashSet<Resource>();
context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, fragsAndReqsResources);
}
for (Bundle b : bundles)
{
//skip bundles that are not installed
if (b.getState() == Bundle.UNINSTALLED)
continue;
//add to context attribute storing associated fragments and required bundles
fragsAndReqsBundles.add(b);
File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(b);
Resource r = Resource.newResource(f.toURI());
//add to convenience context attribute storing fragments and required bundles as Resources
fragsAndReqsResources.add(r);
mergedResources.add(r);
}
}
return mergedResources;
}
/* ------------------------------------------------------------ */
/**
* Allow fragments to supply some resources that are added to the baseResource of the webapp.
*
* The resources can be either prepended or appended to the baseResource.
*
* @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void configure(WebAppContext context) throws Exception
{
TreeMap<String, Resource> prependedResourcesPath = new TreeMap<String, Resource>();
TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>();
Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
if (bundle != null)
{
Set<Bundle> fragments = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
if (fragments != null && !fragments.isEmpty())
{
// sorted extra resource base found in the fragments.
// the resources are either overriding the resourcebase found in the
// web-bundle
// or appended.
// amongst each resource we sort them according to the alphabetical
// order
// of the name of the internal folder and the symbolic name of the
// fragment.
// this is useful to make sure that the lookup path of those
// resource base defined by fragments is always the same.
// This natural order could be abused to define the order in which
// the base resources are
// looked up.
for (Bundle frag : fragments)
{
String path = Util.getManifestHeaderValue(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH,OSGiWebappConstants.JETTY_WAR_FRAGMENT_RESOURCE_PATH,frag.getHeaders());
convertFragmentPathToResource(path, frag, appendedResourcesPath);
path = Util.getManifestHeaderValue(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH, OSGiWebappConstants.JETTY_WAR_PREPEND_FRAGMENT_RESOURCE_PATH, frag.getHeaders());
convertFragmentPathToResource(path, frag, prependedResourcesPath);
}
if (!appendedResourcesPath.isEmpty())
{
LinkedHashSet<Resource> resources = new LinkedHashSet<Resource>();
//Add in any existing setting of extra resource dirs
Set<Resource> resourceDirs = (Set<Resource>)context.getAttribute(WebInfConfiguration.RESOURCE_DIRS);
if (resourceDirs != null && !resourceDirs.isEmpty())
resources.addAll(resourceDirs);
//Then append the values from JETTY_WAR_FRAGMENT_FOLDER_PATH
resources.addAll(appendedResourcesPath.values());
context.setAttribute(WebInfConfiguration.RESOURCE_DIRS, resources);
}
}
}
super.configure(context);
// place the prepended resources at the beginning of the contexts's resource base
if (!prependedResourcesPath.isEmpty())
{
Resource[] resources = new Resource[1+prependedResourcesPath.size()];
System.arraycopy(prependedResourcesPath.values().toArray(new Resource[prependedResourcesPath.size()]), 0, resources, 0, prependedResourcesPath.size());
resources[resources.length-1] = context.getBaseResource();
context.setBaseResource(new ResourceCollection(resources));
}
}
/* ------------------------------------------------------------ */
/**
* Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars
* embedded in the bundle.
*/
private List<Resource> getBundleAsResource(Bundle bundle)
throws Exception
{
List<Resource> resources = new ArrayList<Resource>();
File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
if (file.isDirectory())
{
for (File f : file.listFiles())
{
if (f.getName().endsWith(".jar") && f.isFile())
{
resources.add(Resource.newResource(f));
}
else if (f.isDirectory() && f.getName().equals("lib"))
{
for (File f2 : file.listFiles())
{
if (f2.getName().endsWith(".jar") && f2.isFile())
{
resources.add(Resource.newResource(f));
}
}
}
}
resources.add(Resource.newResource(file)); //TODO really???
}
else
{
resources.add(Resource.newResource(file));
}
return resources;
}
/**
* Convert a path inside a fragment into a Resource
* @param resourcePath
* @param fragment
* @param resourceMap
* @throws Exception
*/
private void convertFragmentPathToResource (String resourcePath, Bundle fragment, Map<String, Resource> resourceMap )
throws Exception
{
if (resourcePath == null)
return;
URL url = fragment.getEntry(resourcePath);
if (url == null)
{
throw new IllegalArgumentException("Unable to locate " + resourcePath
+ " inside "
+ " the fragment '"
+ fragment.getSymbolicName()
+ "'");
}
url = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(url);
String key = resourcePath.startsWith("/") ? resourcePath.substring(1) : resourcePath;
resourceMap.put(key + ";" + fragment.getSymbolicName(), Resource.newResource(url));
}
}