WW-3958 Updates Apache Felix and adds option to cooperate with Felix integrated in Glassfish

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@1484815 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Lukasz Lenart
2013-05-21 14:14:03 +00:00
parent 7df4af2deb
commit 1c5b931cfb
25 changed files with 891 additions and 523 deletions
@@ -26,9 +26,8 @@ import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.struts2.osgi.BundleAccessor;
import org.apache.struts2.osgi.OsgiHost;
import org.apache.struts2.osgi.host.OsgiHost;
import org.apache.struts2.osgi.StrutsOsgiListener;
import org.apache.struts2.osgi.OsgiConfigurationProvider;
import org.apache.struts2.util.ServletContextAware;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
@@ -40,10 +39,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.HashMap;
import java.util.Enumeration;
import java.util.Dictionary;
public class BundlesAction extends ActionSupport implements ServletContextAware {
+8
View File
@@ -22,6 +22,14 @@
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.shell</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.shell.tui</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
@@ -21,17 +21,17 @@
package org.apache.struts2.osgi;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import org.apache.struts2.osgi.host.OsgiHost;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Set;
public interface BundleAccessor {
String CURRENT_BUNDLE_NAME = "__bundle_name__";
@@ -59,4 +59,5 @@ public interface BundleAccessor {
void setBundleContext(BundleContext bundleContext);
void setOsgiHost(OsgiHost osgiHost);
}
@@ -23,18 +23,19 @@ package org.apache.struts2.osgi;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import java.net.URL;
import java.util.Enumeration;
import java.util.Collections;
import java.io.IOException;
import java.io.InputStream;
import org.osgi.framework.Bundle;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* ClassLoaderInterface instance that delegates to the singleton of DefaultBundleAccessor
*/
public class BundleClassLoaderInterface implements ClassLoaderInterface {
public Class<?> loadClass(String name) throws ClassNotFoundException {
return DefaultBundleAccessor.getInstance().loadClass(name);
}
@@ -44,6 +45,10 @@ public class BundleClassLoaderInterface implements ClassLoaderInterface {
}
public Enumeration<URL> getResources(String name) throws IOException {
Collection<URL> coll = DefaultBundleAccessor.getInstance().loadResources(name, true);
if (coll == null){
return new Hashtable<Object, URL>().elements();
}
return Collections.enumeration(DefaultBundleAccessor.getInstance().loadResources(name, true));
}
@@ -54,4 +59,5 @@ public class BundleClassLoaderInterface implements ClassLoaderInterface {
public ClassLoaderInterface getParent() {
return null;
}
}
@@ -21,7 +21,7 @@
package org.apache.struts2.osgi;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
@@ -49,13 +49,14 @@ public class BundlePackageLoader implements PackageLoader {
private static final Logger LOG = LoggerFactory.getLogger(BundlePackageLoader.class);
public List<PackageConfig> loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory,
FileManager fileManager, Map<String, PackageConfig> pkgConfigs) throws ConfigurationException {
FileManagerFactory fileManagerFactory, Map<String, PackageConfig> pkgConfigs) throws ConfigurationException {
Configuration config = new DefaultConfiguration("struts.xml");
BundleConfigurationProvider prov = new BundleConfigurationProvider("struts.xml", bundle, bundleContext);
for (PackageConfig pkg : pkgConfigs.values()) {
config.addPackageConfig(pkg.getName(), pkg);
}
prov.setObjectFactory(objectFactory);
prov.setFileManagerFactory(fileManagerFactory);
prov.init(config);
prov.loadPackages();
@@ -21,25 +21,28 @@
package org.apache.struts2.osgi;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.*;
import java.util.Map.Entry;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.struts2.osgi.host.OsgiHost;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Helper class that find resources and loads classes from the list of bundles
@@ -139,8 +142,10 @@ public class DefaultBundleAccessor implements BundleAccessor {
if (bundle != null) {
List<URL> resources = new ArrayList<URL>();
Enumeration e = bundle.getResources(name);
while (e.hasMoreElements()) {
resources.add(translate ? OsgiUtil.translateBundleURLToJarURL((URL) e.nextElement(), getCurrentBundle()) : (URL) e.nextElement());
if (e != null) {
while (e.hasMoreElements()) {
resources.add(translate ? OsgiUtil.translateBundleURLToJarURL((URL) e.nextElement(), getCurrentBundle()) : (URL) e.nextElement());
}
}
return resources;
}
@@ -151,7 +156,7 @@ public class DefaultBundleAccessor implements BundleAccessor {
public URL loadResourceFromAllBundles(String name) throws IOException {
for (Map.Entry<String, Bundle> entry : osgiHost.getActiveBundles().entrySet()) {
Enumeration e = entry.getValue().getResources(name);
if (e.hasMoreElements()) {
if (e != null && e.hasMoreElements()) {
return (URL) e.nextElement();
}
}
@@ -208,4 +213,5 @@ public class DefaultBundleAccessor implements BundleAccessor {
public void setOsgiHost(OsgiHost osgiHost) {
this.osgiHost = osgiHost;
}
}
@@ -21,15 +21,16 @@
package org.apache.struts2.osgi;
import java.util.Map;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.PackageProvider;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.struts2.util.ObjectFactoryDestroyable;
import java.util.Map;
public class DelegatingObjectFactory extends ObjectFactory implements ObjectFactoryDestroyable {
private ObjectFactory delegateObjectFactory;
private BundleAccessor bundleResourceLoader;
private OsgiConfigurationProvider osgiConfigurationProvider;
@@ -88,4 +89,5 @@ public class DelegatingObjectFactory extends ObjectFactory implements ObjectFact
public void setOsgiConfigurationProvider(PackageProvider osgiConfigurationProvider) {
this.osgiConfigurationProvider = (OsgiConfigurationProvider) osgiConfigurationProvider;
}
}
@@ -22,7 +22,6 @@
package org.apache.struts2.osgi;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
@@ -34,6 +33,7 @@ import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.osgi.host.OsgiHost;
import org.apache.struts2.osgi.loaders.VelocityBundleResourceLoader;
import org.apache.struts2.views.velocity.VelocityManager;
import org.apache.velocity.app.Velocity;
@@ -52,11 +52,12 @@ import java.util.Set;
* Struts package provider that starts the OSGi container and deelgates package loading
*/
public class OsgiConfigurationProvider implements PackageProvider, BundleListener {
private static final Logger LOG = LoggerFactory.getLogger(OsgiConfigurationProvider.class);
private Configuration configuration;
private ObjectFactory objectFactory;
private FileManager fileManager;
private FileManagerFactory fileManagerFactory;
private OsgiHost osgiHost;
private BundleContext bundleContext;
@@ -81,10 +82,10 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
if (LOG.isTraceEnabled())
LOG.trace("Loading packages from XML and Convention on startup");
//init action contect
//init action context
ActionContext ctx = ActionContext.getContext();
if (ctx == null) {
ctx = new ActionContext(new HashMap());
ctx = createActionContext();
ActionContext.setContext(ctx);
}
@@ -95,7 +96,7 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
String bundleName = bundle.getSymbolicName();
if (shouldProcessBundle(bundle) && !bundleNames.contains(bundleName)) {
bundleNames.add(bundleName);
//load XML and COnvention config
//load XML and Convention config
loadConfigFromBundle(bundle);
}
}
@@ -104,6 +105,10 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
bundleContext.addBundleListener(this);
}
protected ActionContext createActionContext() {
return new ActionContext(new HashMap<String, Object>());
}
/**
* Loads XML config as well as Convention config from a bundle
* Limitation: Constants and Beans are ignored on XML config
@@ -117,7 +122,7 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
//init action context
ActionContext ctx = ActionContext.getContext();
if (ctx == null) {
ctx = new ActionContext(new HashMap());
ctx = createActionContext();
ActionContext.setContext(ctx);
}
@@ -133,14 +138,14 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
//XML config
PackageLoader loader = new BundlePackageLoader();
for (PackageConfig pkg : loader.loadPackages(bundle, bundleContext, objectFactory, fileManager, configuration.getPackageConfigs())) {
for (PackageConfig pkg : loader.loadPackages(bundle, bundleContext, objectFactory, fileManagerFactory, configuration.getPackageConfigs())) {
configuration.addPackageConfig(pkg.getName(), pkg);
bundleAccessor.addPackageFromBundle(bundle, pkg.getName());
}
//Convention
//get the existing packages before reloading the provider (se we can figure out what are the new packages)
Set<String> packagesBeforeLoading = new HashSet(configuration.getPackageConfigNames());
Set<String> packagesBeforeLoading = new HashSet<String>(configuration.getPackageConfigNames());
PackageProvider conventionPackageProvider = configuration.getContainer().getInstance(PackageProvider.class, "convention.packageProvider");
if (conventionPackageProvider != null) {
@@ -149,7 +154,7 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
conventionPackageProvider.loadPackages();
}
Set<String> packagesAfterLoading = new HashSet(configuration.getPackageConfigNames());
Set<String> packagesAfterLoading = new HashSet<String>(configuration.getPackageConfigNames());
packagesAfterLoading.removeAll(packagesBeforeLoading);
if (!packagesAfterLoading.isEmpty()) {
//add the new packages to the map of bundle -> package
@@ -172,7 +177,7 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
* Checks for "Struts2-Enabled" header in the bundle
*/
protected boolean shouldProcessBundle(Bundle bundle) {
String strutsEnabled = (String) bundle.getHeaders().get(OsgiHost.OSGI_HEADER_STRUTS_ENABLED);
String strutsEnabled = bundle.getHeaders().get(OsgiHost.OSGI_HEADER_STRUTS_ENABLED);
return "true".equalsIgnoreCase(strutsEnabled);
}
@@ -205,16 +210,18 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
this.servletContext = servletContext;
}
@Inject
public void setFileManagerFactory(FileManagerFactory fmFactory) {
this.fileManager = fmFactory.getFileManager();
this.fileManagerFactory = fmFactory;
}
public void destroy() {
try {
osgiHost.destroy();
} catch (Exception e) {
if (LOG.isErrorEnabled())
if (LOG.isErrorEnabled()) {
LOG.error("Failed to stop OSGi container", e);
}
}
}
@@ -246,10 +253,13 @@ public class OsgiConfigurationProvider implements PackageProvider, BundleListene
protected void onBundleStopped(Bundle bundle) {
Set<String> packages = bundleAccessor.getPackagesByBundle(bundle);
if (!packages.isEmpty()) {
if (LOG.isTraceEnabled())
if (LOG.isTraceEnabled()) {
LOG.trace("The bundle [#0] has been stopped. The packages [#1] will be disabled", bundle.getSymbolicName(), StringUtils.join(packages, ","));
for (String packageName : packages)
}
for (String packageName : packages) {
configuration.removePackageConfig(packageName);
}
}
}
}
@@ -0,0 +1,49 @@
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.osgi;
import org.apache.struts2.osgi.host.FelixOsgiHost;
import org.apache.struts2.osgi.host.GlassfishOSGiHost;
import org.apache.struts2.osgi.host.OsgiHost;
/**
* OsgiHostFactory that creates proper OsgiHost implementation according to
* context param from web.xml
* <p/>
* Two implementations are supported right now:
* - Apache Felix
* - Glassfish (which contains Apache Felix already)
*/
public class OsgiHostFactory {
public static final String GLASSFISH = "Glassfish";
private OsgiHostFactory() {
}
public static OsgiHost createOsgiHost(String platform) {
if (GLASSFISH.equalsIgnoreCase(platform)) {
return new GlassfishOSGiHost();
}
return new FelixOsgiHost();
}
}
@@ -21,19 +21,16 @@
package org.apache.struts2.osgi;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.MalformedURLException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Bundle;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.osgi.framework.Bundle;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
public class OsgiUtil {
private static final Logger LOG = LoggerFactory.getLogger(OsgiUtil.class);
/**
@@ -21,7 +21,7 @@
package org.apache.struts2.osgi;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.PackageConfig;
@@ -36,7 +36,8 @@ import java.util.Map;
*/
public interface PackageLoader {
List<PackageConfig> loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory, FileManager fileManager,
List<PackageConfig> loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory,
FileManagerFactory fileManagerFactory,
Map<String, PackageConfig> map) throws ConfigurationException;
}
@@ -33,6 +33,7 @@ import java.util.Map;
* to lookup beans
*/
public class SpringOsgiObjectFactory extends ObjectFactory {
private final static String SPRING_SERVICE_NAME = "org.springframework.context.ApplicationContext";
private BundleAccessor bundleAccessor;
@@ -88,4 +89,5 @@ public class SpringOsgiObjectFactory extends ObjectFactory {
public void setBundleAccessor(BundleAccessor bundleAccessor) {
this.bundleAccessor = bundleAccessor;
}
}
@@ -1,26 +1,39 @@
package org.apache.struts2.osgi;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.struts2.StrutsException;
import org.apache.struts2.osgi.host.OsgiHost;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* ServletContextListener that starts Apache Felix
* ServletContextListener that starts Osgi host
*/
public class StrutsOsgiListener implements ServletContextListener {
public static final String OSGI_HOST = "__struts_osgi_host";
private FelixOsgiHost osgiHost;
public static final String PLATFORM_KEY = "struts.osgi.host";
private static final Logger LOG = LoggerFactory.getLogger(StrutsOsgiListener.class);
private OsgiHost osgiHost;
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
osgiHost = new FelixOsgiHost();
String platform = servletContext.getInitParameter(PLATFORM_KEY);
if (LOG.isDebugEnabled()) {
LOG.debug("Defined OSGi platform as [#0] via context-param [#1]", platform, PLATFORM_KEY);
}
osgiHost = OsgiHostFactory.createOsgiHost(platform);
servletContext.setAttribute(OSGI_HOST, osgiHost);
try {
osgiHost.init(servletContext);
} catch (Exception e) {
throw new StrutsException("Apache Felix failed to start", e);
throw new StrutsException("Cannot init OSGi platform!", e);
}
}
@@ -28,7 +41,8 @@ public class StrutsOsgiListener implements ServletContextListener {
try {
osgiHost.destroy();
} catch (Exception e) {
throw new StrutsException("Apache Felix failed to stop", e);
throw new StrutsException("Cannot stop OSGi platform!", e);
}
}
}
}
@@ -1,427 +1,329 @@
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.osgi;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.util.finder.ResourceFinder;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.framework.Felix;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.main.AutoActivator;
import org.apache.felix.main.Main;
import org.apache.felix.shell.ShellService;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsException;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.util.tracker.ServiceTracker;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Apache felix implementation of an OsgiHost
* See http://felix.apache.org/site/apache-felix-framework-launching-and-embedding.html
* <br/>
* Servlet config params:
* <p>struts.osgi.clearBundleCache: Defaults to "true" delete installed bundles when the comntainer starts</p>
* <p>struts.osgi.logLevel: Defaults to "1". Felix log level. 1 = error, 2 = warning, 3 = information, and 4 = debug </p>
* <p>struts.osgi.runLevel: Defaults to "3". Run level to start the container.</p>
*/
public class FelixOsgiHost implements OsgiHost {
private static final Logger LOG = LoggerFactory.getLogger(FelixOsgiHost.class);
private Felix felix;
private static final Pattern versionPattern = Pattern.compile("([\\d])+[\\.-]");
private ServletContext servletContext;
protected void startFelix() {
//load properties from felix embedded file
Properties configProps = getProperties("default.properties");
// Copy framework properties from the system properties.
Main.copySystemProperties(configProps);
replaceSystemPackages(configProps);
//struts, xwork and felix exported packages
Properties strutsConfigProps = getProperties("struts-osgi.properties");
addExportedPackages(strutsConfigProps, configProps);
//find bundles and adde em to autostart property
addAutoStartBundles(configProps);
// Bundle cache
String storageDir = System.getProperty("java.io.tmpdir") + ".felix-cache";
configProps.setProperty(Constants.FRAMEWORK_STORAGE, storageDir);
if (LOG.isDebugEnabled())
LOG.debug("Storing bundles at [#0]", storageDir);
String cleanBundleCache = getServletContextParam("struts.osgi.clearBundleCache", "true");
if ("true".equalsIgnoreCase(cleanBundleCache)) {
if (LOG.isDebugEnabled())
LOG.debug("Clearing bundle cache");
configProps.put(FelixConstants.FRAMEWORK_STORAGE_CLEAN, FelixConstants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
}
//other properties
configProps.put(FelixConstants.SERVICE_URLHANDLERS_PROP, "false");
configProps.put(FelixConstants.LOG_LEVEL_PROP, getServletContextParam("struts.osgi.logLevel", "1"));
configProps.put(FelixConstants.BUNDLE_CLASSPATH, ".");
configProps.put(FelixConstants.FRAMEWORK_BEGINNING_STARTLEVEL, getServletContextParam("struts.osgi.runLevel", "3"));
try {
List<BundleActivator> list = new ArrayList<BundleActivator>();
list.add(new AutoActivator(configProps));
configProps.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, list);
felix = new Felix(configProps);
felix.start();
if (LOG.isTraceEnabled())
LOG.trace("Apache Felix is running");
}
catch (Exception ex) {
throw new ConfigurationException("Couldn't start Apache Felix", ex);
}
addSpringOSGiSupport();
//add the bundle context to the ServletContext
servletContext.setAttribute(OSGI_BUNDLE_CONTEXT, felix.getBundleContext());
}
/**
* Gets a param from the ServletContext, returning the default value if the param is not set
*
* @param paramName the name of the param to get from the ServletContext
* @param defaultValue value to return if the param is not set
* @return
*/
private String getServletContextParam(String paramName, String defaultValue) {
return StringUtils.defaultString(this.servletContext.getInitParameter(paramName), defaultValue);
}
protected void addAutoStartBundles(Properties configProps) {
//starts system bundles in level 1
List<String> bundleJarsLevel1 = new ArrayList<String>();
bundleJarsLevel1.add(getJarUrl(ShellService.class));
bundleJarsLevel1.add(getJarUrl(ServiceTracker.class));
configProps.put(AutoActivator.AUTO_START_PROP + ".1", StringUtils.join(bundleJarsLevel1, " "));
//get a list of directories under /bundles with numeric names (the runlevel)
Map<String, String> runLevels = getRunLevelDirs("bundles");
if (runLevels.isEmpty()) {
//there are no run level dirs, search for bundles in that dir
List<String> bundles = getBundlesInDir("bundles");
if (!bundles.isEmpty())
configProps.put(AutoActivator.AUTO_START_PROP + ".2", StringUtils.join(bundles, " "));
} else {
for (String runLevel : runLevels.keySet()) {
if ("1".endsWith(runLevel))
throw new StrutsException("Run level dirs must be greater than 1. Run level 1 is reserved for the Felix bundles");
List<String> bundles = getBundlesInDir(runLevels.get(runLevel));
configProps.put(AutoActivator.AUTO_START_PROP + "." + runLevel, StringUtils.join(bundles, " "));
}
}
}
/**
* Return a list of directories under a directory whose name is a number
*/
protected Map<String, String> getRunLevelDirs(String dir) {
Map<String, String> dirs = new HashMap<String, String>();
try {
ResourceFinder finder = new ResourceFinder();
URL url = finder.find("bundles");
if (url != null) {
if ("file".equals(url.getProtocol())) {
File bundlesDir = new File(url.toURI());
String[] runLevelDirs = bundlesDir.list(new FilenameFilter() {
public boolean accept(File file, String name) {
try {
return file.isDirectory() && Integer.valueOf(name) > 0;
} catch (NumberFormatException ex) {
//the name is not a number
return false;
}
}
});
if (runLevelDirs != null && runLevelDirs.length > 0) {
//add all the dirs to the list
for (String runLevel : runLevelDirs)
dirs.put(runLevel, StringUtils.chomp(dir, "/") + "/" + runLevel);
} else if (LOG.isDebugEnabled()) {
LOG.debug("No run level directories found under the [#0] directory", dir);
}
} else if (LOG.isWarnEnabled())
LOG.warn("Unable to read [#0] directory", dir);
} else if (LOG.isWarnEnabled())
LOG.warn("The [#0] directory was not found", dir);
} catch (Exception e) {
if (LOG.isWarnEnabled())
LOG.warn("Unable load bundles from the [#0] directory", e, dir);
}
return dirs;
}
protected List<String> getBundlesInDir(String dir) {
List<String> bundleJars = new ArrayList<String>();
try {
ResourceFinder finder = new ResourceFinder();
URL url = finder.find(dir);
if (url != null) {
if ("file".equals(url.getProtocol())) {
File bundlesDir = new File(url.toURI());
File[] bundles = bundlesDir.listFiles(new FilenameFilter() {
public boolean accept(File file, String name) {
return StringUtils.endsWith(name, ".jar");
}
});
if (bundles != null && bundles.length > 0) {
//add all the bundles to the list
for (File bundle : bundles) {
String externalForm = bundle.toURI().toURL().toExternalForm();
if (LOG.isDebugEnabled()) {
LOG.debug("Adding bundle [#0]", externalForm);
}
bundleJars.add(externalForm);
}
} else if (LOG.isDebugEnabled()) {
LOG.debug("No bundles found under the [#0] directory", dir);
}
} else if (LOG.isWarnEnabled())
LOG.warn("Unable to read [#0] directory", dir);
} else if (LOG.isWarnEnabled())
LOG.warn("The [#0] directory was not found", dir);
} catch (Exception e) {
if (LOG.isWarnEnabled())
LOG.warn("Unable load bundles from the [#0] directory", e, dir);
}
return bundleJars;
}
protected void addSpringOSGiSupport() {
// see the javadoc for org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext for more details
// OsgiBundleXmlWebApplicationContext expects the the BundleContext to be set in the ServletContext under the attribute
// OsgiBundleXmlWebApplicationContext.BUNDLE_CONTEXT_ATTRIBUTE
try {
Class clazz = Class.forName("org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext");
String key = (String) clazz.getDeclaredField("BUNDLE_CONTEXT_ATTRIBUTE").get(null);
servletContext.setAttribute(key, felix.getBundleContext());
} catch (ClassNotFoundException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Spring OSGi support is not enabled");
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error("The API of Spring OSGi has changed and the field [#0] is no longer available. The OSGi plugin needs to be updated", e,
"org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext.BUNDLE_CONTEXT_ATTRIBUTE");
}
}
}
protected String getJarUrl(Class clazz) {
ProtectionDomain protectionDomain = clazz.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URL loc = codeSource.getLocation();
return loc.toString();
}
protected void replaceSystemPackages(Properties properties) {
//Felix has a way to load the config file and substitution expressions
//but the method does not have a way to specify the file (other than in an env variable)
//${jre-${java.specification.version}}
String systemPackages = (String) properties.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
String jreVersion = "jre-" + System.getProperty("java.version").substring(0, 3);
systemPackages = systemPackages.replace("${jre-${java.specification.version}}", (String) properties.get(jreVersion));
properties.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
}
/*
Find subpackages of the packages defined in the property file and export them
*/
protected void addExportedPackages(Properties strutsConfigProps, Properties configProps) {
String[] rootPackages = StringUtils.split((String) strutsConfigProps.get("scanning.package.includes"), ",");
ResourceFinder finder = new ResourceFinder(StringUtils.EMPTY);
List<String> exportedPackages = new ArrayList<String>();
//build a list of subpackages
for (String rootPackage : rootPackages) {
try {
String version = null;
if (rootPackage.indexOf(";") > 0) {
String[] splitted = rootPackage.split(";");
rootPackage = splitted[0];
version = splitted[1];
}
Map<URL, Set<String>> subpackagesMap = finder.findPackagesMap(StringUtils.replace(rootPackage.trim(), ".", "/"));
for (Map.Entry<URL, Set<String>> entry : subpackagesMap.entrySet()) {
URL url = entry.getKey();
Set<String> packages = entry.getValue();
//get version if not set
if (StringUtils.isBlank(version))
version = getVersion(url);
if (packages != null) {
for (String subpackage : packages) {
exportedPackages.add(subpackage + "; version=" + version);
}
}
}
} catch (IOException e) {
if (LOG.isErrorEnabled())
LOG.error("Unable to find subpackages of [#0]", e, rootPackage);
}
}
//make a string with the exported packages and add it to the system properties
if (!exportedPackages.isEmpty()) {
String systemPackages = (String) configProps.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
systemPackages = StringUtils.chomp(systemPackages, ",") + "," + StringUtils.join(exportedPackages, ",");
configProps.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
}
}
/**
* Gets the version used to export the packages. it tries to get it from MANIFEST.MF, or the file name
*/
protected String getVersion(URL url) {
if ("jar".equals(url.getProtocol())) {
try {
FileManager fileManager = ServletActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager();
JarFile jarFile = new JarFile(new File(fileManager.normalizeToFileProtocol(url).toURI()));
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
String version = manifest.getMainAttributes().getValue("Bundle-Version");
if (StringUtils.isNotBlank(version)) {
return getVersionFromString(version);
}
} else {
//try to get the version from the file name
return getVersionFromString(jarFile.getName());
}
} catch (Exception e) {
if (LOG.isErrorEnabled())
LOG.error("Unable to extract version from [#0], defaulting to '1.0.0'", url.toExternalForm());
}
}
return "1.0.0";
}
/**
* Extracts numbers followed by "." or "-" from the string and joins them with "."
*/
protected static String getVersionFromString(String str) {
Matcher matcher = versionPattern.matcher(str);
List<String> parts = new ArrayList<String>();
while (matcher.find()) {
parts.add(matcher.group(1));
}
//default
if (parts.size() == 0)
return "1.0.0";
while (parts.size() < 3)
parts.add("0");
return StringUtils.join(parts, ".");
}
protected Properties getProperties(String fileName) {
ResourceFinder finder = new ResourceFinder("");
try {
return finder.findProperties(fileName);
} catch (IOException e) {
if (LOG.isErrorEnabled())
LOG.error("Unable to read property file [#]", fileName);
return new Properties();
}
}
/**
* This bundle map will not change, but the status of the bundles can change over time.
* Use getActiveBundles() for active bundles
*/
public Map<String, Bundle> getBundles() {
Map<String, Bundle> bundles = new HashMap<String, Bundle>();
for (Bundle bundle : felix.getBundleContext().getBundles()) {
bundles.put(bundle.getSymbolicName(), bundle);
}
return Collections.unmodifiableMap(bundles);
}
public Map<String, Bundle> getActiveBundles() {
Map<String, Bundle> bundles = new HashMap<String, Bundle>();
for (Bundle bundle : felix.getBundleContext().getBundles()) {
if (bundle.getState() == Bundle.ACTIVE)
bundles.put(bundle.getSymbolicName(), bundle);
}
return Collections.unmodifiableMap(bundles);
}
public BundleContext getBundleContext() {
return felix.getBundleContext();
}
public void destroy() throws Exception {
felix.stop();
if (LOG.isTraceEnabled())
LOG.trace("Apache Felix has stopped");
}
public void init(ServletContext servletContext) {
this.servletContext = servletContext;
startFelix();
}
}
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.osgi.host;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.util.finder.ResourceFinder;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.main.AutoProcessor;
import org.apache.felix.shell.ShellService;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsException;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.util.tracker.ServiceTracker;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A base OsgiHost implementation
*/
public abstract class BaseOsgiHost implements OsgiHost {
private static final Logger LOG = LoggerFactory.getLogger(BaseOsgiHost.class);
protected static final Pattern versionPattern = Pattern.compile("([\\d])+[\\.-]");
protected ServletContext servletContext;
public abstract void init(ServletContext servletContext);
public abstract void destroy() throws Exception;
/**
* This bundle map will not change, but the status of the bundles can change over time.
* Use getActiveBundles() for active bundles
*/
public abstract Map<String, Bundle> getBundles();
public abstract Map<String, Bundle> getActiveBundles();
public abstract BundleContext getBundleContext();
protected abstract void addSpringOSGiSupport();
/**
* Gets a param from the ServletContext, returning the default value if the param is not set
*
* @param paramName the name of the param to get from the ServletContext
* @param defaultValue value to return if the param is not set
* @return
*/
protected String getServletContextParam(String paramName, String defaultValue) {
return StringUtils.defaultString(this.servletContext.getInitParameter(paramName), defaultValue);
}
protected void addAutoStartBundles(Properties configProps) {
//starts system bundles in level 1
List<String> bundleJarsLevel1 = new ArrayList<String>();
bundleJarsLevel1.add(getJarUrl(ShellService.class));
bundleJarsLevel1.add(getJarUrl(ServiceTracker.class));
configProps.put(AutoProcessor.AUTO_START_PROP + ".1", StringUtils.join(bundleJarsLevel1, " "));
//get a list of directories under /bundles with numeric names (the runlevel)
Map<String, String> runLevels = getRunLevelDirs("bundles");
if (runLevels.isEmpty()) {
//there are no run level dirs, search for bundles in that dir
List<String> bundles = getBundlesInDir("bundles");
if (!bundles.isEmpty()) {
configProps.put(AutoProcessor.AUTO_START_PROP + ".2", StringUtils.join(bundles, " "));
}
} else {
for (String runLevel : runLevels.keySet()) {
if ("1".endsWith(runLevel)) {
throw new StrutsException("Run level dirs must be greater than 1. Run level 1 is reserved for the Felix bundles");
}
List<String> bundles = getBundlesInDir(runLevels.get(runLevel));
configProps.put(AutoProcessor.AUTO_START_PROP + "." + runLevel, StringUtils.join(bundles, " "));
}
}
}
/**
* Return a list of directories under a directory whose name is a number
*/
protected Map<String, String> getRunLevelDirs(String dir) {
Map<String, String> dirs = new HashMap<String, String>();
try {
ResourceFinder finder = new ResourceFinder();
URL url = finder.find("bundles");
if (url != null) {
if ("file".equals(url.getProtocol())) {
File bundlesDir = new File(url.toURI());
String[] runLevelDirs = bundlesDir.list(new FilenameFilter() {
public boolean accept(File file, String name) {
try {
return file.isDirectory() && Integer.valueOf(name) > 0;
} catch (NumberFormatException ex) {
//the name is not a number
return false;
}
}
});
if (runLevelDirs != null && runLevelDirs.length > 0) {
//add all the dirs to the list
for (String runLevel : runLevelDirs) {
dirs.put(runLevel, StringUtils.chomp(dir, "/") + "/" + runLevel);
}
} else if (LOG.isDebugEnabled()) {
LOG.debug("No run level directories found under the [#0] directory", dir);
}
} else if (LOG.isWarnEnabled()) {
LOG.warn("Unable to read [#0] directory", dir);
}
} else if (LOG.isWarnEnabled()) {
LOG.warn("The [#0] directory was not found", dir);
}
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Unable load bundles from the [#0] directory", e, dir);
}
}
return dirs;
}
protected List<String> getBundlesInDir(String dir) {
List<String> bundleJars = new ArrayList<String>();
try {
ResourceFinder finder = new ResourceFinder();
URL url = finder.find(dir);
if (url != null) {
if ("file".equals(url.getProtocol())) {
File bundlesDir = new File(url.toURI());
File[] bundles = bundlesDir.listFiles(new FilenameFilter() {
public boolean accept(File file, String name) {
return StringUtils.endsWith(name, ".jar");
}
});
if (bundles != null && bundles.length > 0) {
//add all the bundles to the list
for (File bundle : bundles) {
String externalForm = bundle.toURI().toURL().toExternalForm();
if (LOG.isDebugEnabled()) {
LOG.debug("Adding bundle [#0]", externalForm);
}
bundleJars.add(externalForm);
}
} else if (LOG.isDebugEnabled()) {
LOG.debug("No bundles found under the [#0] directory", dir);
}
} else if (LOG.isWarnEnabled()) {
LOG.warn("Unable to read [#0] directory", dir);
}
} else if (LOG.isWarnEnabled()) {
LOG.warn("The [#0] directory was not found", dir);
}
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Unable load bundles from the [#0] directory", e, dir);
}
}
return bundleJars;
}
protected String getJarUrl(Class clazz) {
ProtectionDomain protectionDomain = clazz.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URL loc = codeSource.getLocation();
return loc.toString();
}
protected void replaceSystemPackages(Properties properties) {
//Felix has a way to load the config file and substitution expressions
//but the method does not have a way to specify the file (other than in an env variable)
//${jre-${java.specification.version}}
String systemPackages = (String) properties.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
String jreVersion = "jre-" + System.getProperty("java.version").substring(0, 3);
systemPackages = systemPackages.replace("${jre-${java.specification.version}}", (String) properties.get(jreVersion));
properties.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
}
/*
Find subpackages of the packages defined in the property file and export them
*/
protected void addExportedPackages(Properties strutsConfigProps, Properties configProps) {
String[] rootPackages = StringUtils.split((String) strutsConfigProps.get("scanning.package.includes"), ",");
ResourceFinder finder = new ResourceFinder(StringUtils.EMPTY);
List<String> exportedPackages = new ArrayList<String>();
//build a list of subpackages
for (String rootPackage : rootPackages) {
try {
String version = null;
if (rootPackage.indexOf(";") > 0) {
String[] splitted = rootPackage.split(";");
rootPackage = splitted[0];
version = splitted[1];
}
Map<URL, Set<String>> subpackagesMap = finder.findPackagesMap(StringUtils.replace(rootPackage.trim(), ".", "/"));
for (Map.Entry<URL, Set<String>> entry : subpackagesMap.entrySet()) {
URL url = entry.getKey();
Set<String> packages = entry.getValue();
//get version if not set
if (StringUtils.isBlank(version)) {
version = getVersion(url);
}
if (packages != null) {
for (String subpackage : packages) {
exportedPackages.add(subpackage + "; version=" + version);
}
}
}
} catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Unable to find subpackages of [#0]", e, rootPackage);
}
}
}
//make a string with the exported packages and add it to the system properties
if (!exportedPackages.isEmpty()) {
String systemPackages = (String) configProps.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
systemPackages = StringUtils.chomp(systemPackages, ",") + "," + StringUtils.join(exportedPackages, ",");
configProps.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
}
}
/**
* Gets the version used to export the packages. it tries to get it from MANIFEST.MF, or the file name
*/
protected String getVersion(URL url) {
if ("jar".equals(url.getProtocol())) {
try {
FileManager fileManager = ServletActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager();
JarFile jarFile = new JarFile(new File(fileManager.normalizeToFileProtocol(url).toURI()));
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
String version = manifest.getMainAttributes().getValue("Bundle-Version");
if (StringUtils.isNotBlank(version)) {
return getVersionFromString(version);
}
} else {
//try to get the version from the file name
return getVersionFromString(jarFile.getName());
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error("Unable to extract version from [#0], defaulting to '1.0.0'", url.toExternalForm());
}
}
}
return "1.0.0";
}
/**
* Extracts numbers followed by "." or "-" from the string and joins them with "."
*/
protected String getVersionFromString(String str) {
Matcher matcher = versionPattern.matcher(str);
List<String> parts = new ArrayList<String>();
while (matcher.find()) {
parts.add(matcher.group(1));
}
//default
if (parts.size() == 0) {
return "1.0.0";
}
while (parts.size() < 3) {
parts.add("0");
}
return StringUtils.join(parts, ".");
}
protected Properties getProperties(String fileName) {
ResourceFinder finder = new ResourceFinder("");
try {
return finder.findProperties(fileName);
} catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Unable to read property file [#]", fileName);
}
return new Properties();
}
}
}
@@ -0,0 +1,173 @@
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.osgi.host;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.felix.framework.Felix;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.main.AutoProcessor;
import org.apache.felix.main.Main;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import javax.servlet.ServletContext;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Apache felix implementation of an OsgiHost
* See http://felix.apache.org/site/apache-felix-framework-launching-and-embedding.html
* <br/>
* Servlet config params:
* <p>struts.osgi.clearBundleCache: Defaults to "true" delete installed bundles when the comntainer starts</p>
* <p>struts.osgi.logLevel: Defaults to "1". Felix log level. 1 = error, 2 = warning, 3 = information, and 4 = debug </p>
* <p>struts.osgi.runLevel: Defaults to "3". Run level to start the container.</p>
*/
public class FelixOsgiHost extends BaseOsgiHost {
private static final Logger LOG = LoggerFactory.getLogger(FelixOsgiHost.class);
protected Felix felix;
protected void startFelix() {
//load properties from felix embedded file
Properties configProps = getProperties("default.properties");
// Copy framework properties from the system properties.
Main.copySystemProperties(configProps);
replaceSystemPackages(configProps);
//struts, xwork and felix exported packages
Properties strutsConfigProps = getProperties("struts-osgi.properties");
addExportedPackages(strutsConfigProps, configProps);
//find bundles and adde em to autostart property
addAutoStartBundles(configProps);
// Bundle cache
String storageDir = System.getProperty("java.io.tmpdir") + ".felix-cache";
configProps.setProperty(Constants.FRAMEWORK_STORAGE, storageDir);
if (LOG.isDebugEnabled())
LOG.debug("Storing bundles at [#0]", storageDir);
String cleanBundleCache = getServletContextParam("struts.osgi.clearBundleCache", "true");
if ("true".equalsIgnoreCase(cleanBundleCache)) {
if (LOG.isDebugEnabled())
LOG.debug("Clearing bundle cache");
configProps.put(FelixConstants.FRAMEWORK_STORAGE_CLEAN, FelixConstants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
}
//other properties
configProps.put(FelixConstants.SERVICE_URLHANDLERS_PROP, "false");
configProps.put(FelixConstants.LOG_LEVEL_PROP, getServletContextParam("struts.osgi.logLevel", "1"));
configProps.put(FelixConstants.BUNDLE_CLASSPATH, ".");
configProps.put(FelixConstants.FRAMEWORK_BEGINNING_STARTLEVEL, getServletContextParam("struts.osgi.runLevel", "3"));
try {
felix = new Felix(configProps);
felix.init();
AutoProcessor.process(configProps, felix.getBundleContext());
felix.start();
if (LOG.isTraceEnabled()) {
LOG.trace("Apache Felix is running");
}
}
catch (Exception ex) {
throw new ConfigurationException("Couldn't start Apache Felix", ex);
}
addSpringOSGiSupport();
//add the bundle context to the ServletContext
servletContext.setAttribute(OSGI_BUNDLE_CONTEXT, felix.getBundleContext());
}
@Override
public void init(ServletContext servletContext) {
this.servletContext = servletContext;
startFelix();
}
@Override
public Map<String, Bundle> getBundles() {
Map<String, Bundle> bundles = new HashMap<String, Bundle>();
for (Bundle bundle : felix.getBundleContext().getBundles()) {
bundles.put(bundle.getSymbolicName(), bundle);
}
return Collections.unmodifiableMap(bundles);
}
@Override
public Map<String, Bundle> getActiveBundles() {
Map<String, Bundle> bundles = new HashMap<String, Bundle>();
for (Bundle bundle : felix.getBundleContext().getBundles()) {
if (bundle.getState() == Bundle.ACTIVE) {
bundles.put(bundle.getSymbolicName(), bundle);
}
}
return Collections.unmodifiableMap(bundles);
}
@Override
public BundleContext getBundleContext() {
return felix.getBundleContext();
}
@Override
public void destroy() throws Exception {
felix.stop();
if (LOG.isTraceEnabled()) {
LOG.trace("Apache Felix has stopped");
}
}
@Override
protected void addSpringOSGiSupport() {
// see the javadoc for org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext for more details
// OsgiBundleXmlWebApplicationContext expects the the BundleContext to be set in the ServletContext under the attribute
// OsgiBundleXmlWebApplicationContext.BUNDLE_CONTEXT_ATTRIBUTE
try {
Class clazz = Class.forName("org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext");
String key = (String) clazz.getDeclaredField("BUNDLE_CONTEXT_ATTRIBUTE").get(null);
servletContext.setAttribute(key, felix.getBundleContext());
} catch (ClassNotFoundException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Spring OSGi support is not enabled");
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error("The API of Spring OSGi has changed and the field [#0] is no longer available. The OSGi plugin needs to be updated", e,
"org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext.BUNDLE_CONTEXT_ATTRIBUTE");
}
}
}
}
@@ -0,0 +1,181 @@
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.osgi.host;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.felix.shell.ShellService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleReference;
import javax.servlet.ServletContext;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A glassfish implementation of an OsgiHost
*/
public class GlassfishOSGiHost extends BaseOsgiHost implements OsgiHost {
private static final Logger LOG = LoggerFactory.getLogger(GlassfishOSGiHost.class);
/**
* Location inside the WAR where initial bundles are located.
*/
private static final String BUNDLES_DIR = "/WEB-INF/classes/bundles/2/";
private BundleContext bctx = null;
@Override
public void init(ServletContext servletContext) {
this.servletContext = servletContext;
installManagedBundles();
addSpringOSGiSupport();
// add the bundle context to the ServletContext
servletContext.setAttribute(OSGI_BUNDLE_CONTEXT, bctx);
}
private void installManagedBundles() {
try {
// Obtaining BundleContext from ServletContext class which is loaded
// by bundle class loader
BundleReference ref = (BundleReference) servletContext.getClass()
.getClassLoader();
bctx = ref.getBundle().getBundleContext();
// installing managed bundles
installBundles();
} catch (Exception ex) {
LOG.error("Installing Managed Bundles met a problem", ex);
}
}
private void installBundles() throws Exception {
ArrayList<Bundle> installed = new ArrayList<Bundle>();
for (URL url : findBundles()) {
LOG.debug("Installing bundle [" + url + "]");
Bundle bundle = bctx.installBundle(url.toExternalForm());
installed.add(bundle);
}
for (Bundle bundle : installed) {
try {
bundle.start();
} catch (BundleException e) {
e.printStackTrace();
LOG.error("Failed to start " + bundle, e);
}
}
}
private List<URL> findBundles() throws Exception {
ArrayList<URL> list = new ArrayList<URL>();
for (Object o : this.servletContext.getResourcePaths(BUNDLES_DIR)) {
String name = (String) o;
if (name.endsWith(".jar")) {
URL url = this.servletContext.getResource(name);
if (url != null) {
list.add(url);
}
}
}
ProtectionDomain protectionDomain = ShellService.class.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URL loc = codeSource.getLocation();
list.add(loc);
return list;
}
@Override
protected void addSpringOSGiSupport() {
// see the javadoc for
// org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
// for more details
// OsgiBundleXmlWebApplicationContext expects the the BundleContext to
// be set in the ServletContext under the attribute
// OsgiBundleXmlWebApplicationContext.BUNDLE_CONTEXT_ATTRIBUTE
try {
Class<?> clazz = Class
.forName("org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext");
String key = (String) clazz.getDeclaredField(
"BUNDLE_CONTEXT_ATTRIBUTE").get(null);
servletContext.setAttribute(key, bctx);
} catch (ClassNotFoundException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Spring OSGi support is not enabled");
}
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(
"The API of Spring OSGi has changed and the field [#0] is no longer available. The OSGi plugin needs to be updated",
e,
"org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext.BUNDLE_CONTEXT_ATTRIBUTE");
}
}
}
/**
* This bundle map will not change, but the status of the bundles can change
* over time. Use getActiveBundles() for active bundles
*/
@Override
public Map<String, Bundle> getBundles() {
Map<String, Bundle> bundles = new HashMap<String, Bundle>();
for (Bundle bundle : bctx.getBundles()) {
bundles.put(bundle.getSymbolicName(), bundle);
}
return Collections.unmodifiableMap(bundles);
}
public Map<String, Bundle> getActiveBundles() {
Map<String, Bundle> bundles = new HashMap<String, Bundle>();
for (Bundle bundle : bctx.getBundles()) {
if (bundle.getState() == Bundle.ACTIVE)
bundles.put(bundle.getSymbolicName(), bundle);
}
return Collections.unmodifiableMap(bundles);
}
public BundleContext getBundleContext() {
return bctx;
}
public void destroy() throws Exception {
}
}
@@ -19,7 +19,7 @@
* under the License.
*/
package org.apache.struts2.osgi;
package org.apache.struts2.osgi.host;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.Bundle;
@@ -27,5 +27,7 @@ import org.osgi.framework.BundleContext;
* the OsgiInterceptor must be applied to the action.
*/
public interface BundleContextAware {
void setBundleContext(BundleContext bundleContext);
}
@@ -20,28 +20,27 @@
*/
package org.apache.struts2.osgi.interceptor;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.opensymphony.xwork2.inject.Inject;
import javax.servlet.ServletContext;
import org.apache.struts2.osgi.OsgiHost;
import org.apache.struts2.osgi.host.OsgiHost;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import java.lang.reflect.Type;
import javax.servlet.ServletContext;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* If a class implements BundleContextAware, this interceptor will call the setBundleContext(BundleContext)
* method on it. If a class implements ServiceAware<T>, this interceptor will call setService(List<T>)
*/
public class OsgiInterceptor extends AbstractInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(OsgiInterceptor.class);
private BundleContext bundleContext;
@@ -96,4 +95,5 @@ public class OsgiInterceptor extends AbstractInterceptor {
public void setServletContext(ServletContext servletContext) {
this.bundleContext = (BundleContext) servletContext.getAttribute(OsgiHost.OSGI_BUNDLE_CONTEXT);
}
}
@@ -28,5 +28,7 @@ import java.util.List;
* @param <T> The type of the service
*/
public interface ServiceAware<T> {
void setServices(List<T> services);
}
@@ -21,20 +21,19 @@
package org.apache.struts2.osgi.loaders;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.struts2.dispatcher.DefaultStaticContentLoader;
import org.apache.struts2.osgi.DefaultBundleAccessor;
import org.apache.struts2.osgi.BundleAccessor;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.struts2.dispatcher.DefaultStaticContentLoader;
import org.apache.struts2.osgi.BundleAccessor;
import java.io.IOException;
import java.net.URL;
/**
* Loads static resources from bundles
*
*/
public class StaticContentBundleResourceLoader extends DefaultStaticContentLoader {
private BundleAccessor bundleAccessor;
protected URL findResource(String path) throws IOException {
@@ -45,4 +44,5 @@ public class StaticContentBundleResourceLoader extends DefaultStaticContentLoade
public void setBundleAccessor(BundleAccessor bundleAccessor) {
this.bundleAccessor = bundleAccessor;
}
}
@@ -48,4 +48,5 @@ public class VelocityBundleResourceLoader extends ClasspathResourceLoader {
throw new ResourceNotFoundException(e.getMessage());
}
}
}
@@ -18,18 +18,23 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.osgi;
package org.apache.struts2.osgi.host;
import junit.framework.TestCase;
import org.apache.struts2.osgi.host.FelixOsgiHost;
public class FelixOsgiHostTest extends TestCase {
private FelixOsgiHost felixHost = new FelixOsgiHost();
public void testGetVersionFromString() {
assertEquals("2.1.1", FelixOsgiHost.getVersionFromString("2.1.1-SNAPSHOT"));
assertEquals("2.1.1", FelixOsgiHost.getVersionFromString("2.1.1.SNAPSHOT"));
assertEquals("2.1.1", FelixOsgiHost.getVersionFromString("something-2.1.1.SNAPSHOT"));
assertEquals("2.1.1", FelixOsgiHost.getVersionFromString("something-2-1-1.SNAPSHOT"));
assertEquals("2.1.0", FelixOsgiHost.getVersionFromString("something-2-1.SNAPSHOT"));
assertEquals("2.0.0", FelixOsgiHost.getVersionFromString("something-2.SNAPSHOT"));
assertEquals("1.0.0", FelixOsgiHost.getVersionFromString("something"));
assertEquals("2.1.1", felixHost.getVersionFromString("2.1.1-SNAPSHOT"));
assertEquals("2.1.1", felixHost.getVersionFromString("2.1.1.SNAPSHOT"));
assertEquals("2.1.1", felixHost.getVersionFromString("something-2.1.1.SNAPSHOT"));
assertEquals("2.1.1", felixHost.getVersionFromString("something-2-1-1.SNAPSHOT"));
assertEquals("2.1.0", felixHost.getVersionFromString("something-2-1.SNAPSHOT"));
assertEquals("2.0.0", felixHost.getVersionFromString("something-2.SNAPSHOT"));
assertEquals("1.0.0", felixHost.getVersionFromString("something"));
}
}
@@ -1,7 +1,7 @@
package org.apache.struts2.osgi.interceptor;
import org.easymock.EasyMock;
import org.apache.struts2.osgi.OsgiHost;
import org.apache.struts2.osgi.host.OsgiHost;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
+10
View File
@@ -493,6 +493,16 @@
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.shell</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.shell.tui</artifactId>
<version>1.4.1</version>
</dependency>