Move the OSGi plugin to trunk, from the sandbox

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@765209 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Musachy Barroso
2009-04-15 14:15:55 +00:00
parent a39dd7d145
commit cf37766728
26 changed files with 2151 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>2.0.9</version>
</parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-plugin</artifactId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<name>Struts 2 OSGI Plugin</name>
<scm>
<connection>scm:svn:http://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-osgi-plugin/</connection>
<developerConnection>scm:svn:https://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-osgi-plugin/</developerConnection>
<url>http://svn.apache.org/viewcvs.cgi/struts/sandbox/trunk/struts2-osgi-plugin/</url>
</scm>
<build>
<plugins>
<plugin>
<inherited>true</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.1.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>1.3</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>struts</groupId>
<artifactId>struts</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-digester</groupId>
<artifactId>commons-digester</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
<version>2.4</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,62 @@
/*
* $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 java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
public interface BundleAccessor {
String CURRENT_BUNDLE_NAME = "__bundle_name__";
Class loadClass(String name) throws ClassNotFoundException;
InputStream loadResourceAsStream(String name) throws IOException;
InputStream loadResourceFromAllBundlesAsStream(String name) throws IOException;
URL loadResourceFromAllBundles(String name) throws IOException;
Set<String> getPackagesByBundle(Bundle bundle);
Object getService(ServiceReference ref);
ServiceReference getServiceReference(String className);
ServiceReference[] getServiceReferences(String className, String params) throws InvalidSyntaxException;
public ServiceReference[] getAllServiceReferences(String className);
void addPackageFromBundle(Bundle bundle, String packageName);
void setBundleContext(BundleContext bundleContext);
void setOsgiHost(OsgiHost osgiHost);
}
@@ -0,0 +1,57 @@
/*
* $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.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;
/**
* 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);
}
public URL getResource(String name) {
return DefaultBundleAccessor.getInstance().loadResource(name, true);
}
public Enumeration<URL> getResources(String name) throws IOException {
return Collections.enumeration(DefaultBundleAccessor.getInstance().loadResources(name, true));
}
public InputStream getResourceAsStream(String name) throws IOException {
return DefaultBundleAccessor.getInstance().loadResourceAsStream(name);
}
public ClassLoaderInterface getParent() {
return null;
}
}
@@ -0,0 +1,83 @@
/*
* $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 java.io.File;
import java.io.IOException;
import javax.servlet.ServletContext;
import org.apache.struts2.osgi.loaders.FreeMarkerBundleResourceLoader;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.freemarker.StrutsClassTemplateLoader;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.cache.WebappTemplateLoader;
/**
* This class extends FreemarkerManager in core to add a template loader
* (that finds resources inside bundles) to MultiTemplateLoader
*/
public class BundleFreemarkerManager extends FreemarkerManager {
private static final Logger LOG = LoggerFactory.getLogger(BundleFreemarkerManager.class);
@Override
protected TemplateLoader getTemplateLoader(ServletContext servletContext) {
// construct a FileTemplateLoader for the init-param 'TemplatePath'
FileTemplateLoader templatePathLoader = null;
String templatePath = servletContext.getInitParameter("TemplatePath");
if (templatePath == null) {
templatePath = servletContext.getInitParameter("templatePath");
}
if (templatePath != null) {
try {
templatePathLoader = new FileTemplateLoader(new File(templatePath));
} catch (IOException e) {
if (LOG.isErrorEnabled())
LOG.error("Invalid template path specified: [#0]", e, e.getMessage());
}
}
// presume that most apps will require the class and webapp template loader
// if people wish to
return templatePathLoader != null ?
new MultiTemplateLoader(new TemplateLoader[]{
templatePathLoader,
new WebappTemplateLoader(servletContext),
new StrutsClassTemplateLoader(),
new FreeMarkerBundleResourceLoader()
})
: new MultiTemplateLoader(new TemplateLoader[]{
new WebappTemplateLoader(servletContext),
new StrutsClassTemplateLoader(),
new FreeMarkerBundleResourceLoader()
});
}
}
@@ -0,0 +1,107 @@
/*
* $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 java.io.IOException;
import java.net.URL;
import java.util.*;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.PackageProvider;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.impl.DefaultConfiguration;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.util.location.Location;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
/**
* Package loader implementation that loads resources from a bundle
*/
public class BundlePackageLoader implements PackageLoader {
private static final Logger LOG = LoggerFactory.getLogger(BundlePackageLoader.class);
public List<PackageConfig> loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory, 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.init(config);
prov.loadPackages();
List<PackageConfig> list = new ArrayList<PackageConfig>(config.getPackageConfigs().values());
list.removeAll(pkgConfigs.values());
return list;
}
static class BundleConfigurationProvider extends XmlConfigurationProvider {
private Bundle bundle;
private BundleContext bundleContext;
public BundleConfigurationProvider(String filename, Bundle bundle, BundleContext bundleContext) {
super(filename, false);
this.bundle = bundle;
this.bundleContext = bundleContext;
}
public BundleConfigurationProvider(String filename) {
super(filename);
}
@Override
protected Iterator<URL> getConfigurationUrls(String fileName) throws IOException {
Enumeration<URL> e = bundle.getResources("struts.xml");
return e.hasMoreElements() ? new EnumeratorIterator<URL>(e) : null;
}
}
static class EnumeratorIterator<E> implements Iterator<E> {
Enumeration<E> e = null;
public EnumeratorIterator(Enumeration<E> e) {
this.e = e;
}
public boolean hasNext() {
return e.hasMoreElements();
}
public E next() {
return e.nextElement();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
@@ -0,0 +1,211 @@
/*
* $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 java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.*;
import java.util.Map.Entry;
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;
/**
* Helper class that find resources and loads classes from the list of bundles
*/
public class DefaultBundleAccessor implements BundleAccessor {
private static DefaultBundleAccessor self;
private static final Logger LOG = LoggerFactory.getLogger(DefaultBundleAccessor.class);
private BundleContext bundleContext;
private Map<String, String> packageToBundle = new HashMap<String, String>();
private Map<Bundle, Set<String>> packagesByBundle = new HashMap<Bundle, Set<String>>();
private OsgiHost osgiHost;
public DefaultBundleAccessor() {
self = this;
}
public static DefaultBundleAccessor getInstance() {
return self;
}
public Object getService(ServiceReference ref) {
return bundleContext != null ? bundleContext.getService(ref) : null;
}
public ServiceReference getServiceReference(String className) {
return bundleContext != null ? bundleContext.getServiceReference(className) : null;
}
public ServiceReference[] getAllServiceReferences(String className) {
if (bundleContext != null) {
try {
return bundleContext.getServiceReferences(className, null);
} catch (InvalidSyntaxException e) {
//cannot happen we are passing null as the param
if (LOG.isErrorEnabled())
LOG.error("Invalid syntax for service lookup", e);
}
}
return null;
}
public ServiceReference[] getServiceReferences(String className, String params) throws InvalidSyntaxException {
return bundleContext != null ? bundleContext.getServiceReferences(className, params) : null;
}
/**
* Add as Bundle -> Package mapping
* @param bundle the bundle where the package was loaded from
* @param packageName the anme of the loaded package
*/
public void addPackageFromBundle(Bundle bundle, String packageName) {
this.packageToBundle.put(packageName, bundle.getSymbolicName());
Set<String> pkgs = packagesByBundle.get(bundle);
if (pkgs == null) {
pkgs = new HashSet<String>();
packagesByBundle.put(bundle, pkgs);
}
pkgs.add(packageName);
}
public Class<?> loadClass(String className) throws ClassNotFoundException {
Bundle bundle = getCurrentBundle();
if (bundle != null) {
Class cls = bundle.loadClass(className);
if (LOG.isTraceEnabled())
LOG.trace("Located class [#0] in bundle [#1]", className, bundle.getSymbolicName());
return cls;
}
throw new ClassNotFoundException("Unable to find class " + className);
}
private Bundle getCurrentBundle() {
ActionContext ctx = ActionContext.getContext();
String bundleName = (String) ctx.get(CURRENT_BUNDLE_NAME);
if (bundleName == null) {
ActionInvocation inv = ctx.getActionInvocation();
ActionProxy proxy = inv.getProxy();
ActionConfig actionConfig = proxy.getConfig();
bundleName = packageToBundle.get(actionConfig.getPackageName());
}
if (bundleName != null) {
return osgiHost.getActiveBundles().get(bundleName);
}
return null;
}
public List<URL> loadResources(String name) throws IOException {
return loadResources(name, false);
}
public List<URL> loadResources(String name, boolean translate) throws IOException {
Bundle bundle = getCurrentBundle();
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());
}
return resources;
}
return null;
}
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()) {
return (URL) e.nextElement();
}
}
return null;
}
public InputStream loadResourceFromAllBundlesAsStream(String name) throws IOException {
URL url = loadResourceFromAllBundles(name);
if (url != null) {
return url.openStream();
}
return null;
}
public URL loadResource(String name) {
return loadResource(name, false);
}
public URL loadResource(String name, boolean translate) {
Bundle bundle = getCurrentBundle();
if (bundle != null) {
URL url = bundle.getResource(name);
try {
return translate ? OsgiUtil.translateBundleURLToJarURL(url, getCurrentBundle()) : url;
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error("Unable to translate bundle URL to jar URL", e);
}
return null;
}
}
return null;
}
public Set<String> getPackagesByBundle(Bundle bundle) {
return packagesByBundle.get(bundle);
}
public InputStream loadResourceAsStream(String name) throws IOException {
URL url = loadResource(name);
if (url != null) {
return url.openStream();
}
return null;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public void setOsgiHost(OsgiHost osgiHost) {
this.osgiHost = osgiHost;
}
}
@@ -0,0 +1,91 @@
/*
* $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 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;
public class DelegatingObjectFactory extends ObjectFactory implements ObjectFactoryDestroyable {
private ObjectFactory delegateObjectFactory;
private BundleAccessor bundleResourceLoader;
private OsgiConfigurationProvider osgiConfigurationProvider;
@Inject
public void setDelegateObjectFactory(@Inject Container container,
@Inject("struts.objectFactory.delegate") String delegate) {
if (delegate == null) {
delegate = "struts";
}
delegateObjectFactory = container.getInstance(ObjectFactory.class, delegate);
}
@Inject
public void setBundleResourceLoader(BundleAccessor rl) {
this.bundleResourceLoader = rl;
}
public boolean isNoArgConstructorRequired() {
return delegateObjectFactory.isNoArgConstructorRequired();
}
public Object buildBean(Class clazz, Map extraContext) throws Exception {
return delegateObjectFactory.buildBean(clazz, extraContext);
}
public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
try {
return delegateObjectFactory.buildBean(className, extraContext, injectInternal);
} catch (Exception e) {
Object object = bundleResourceLoader.loadClass(className).newInstance();
if (injectInternal)
injectInternalBeans(object);
return object;
}
}
@Override
public Class getClassInstance(String className) throws ClassNotFoundException {
try {
return delegateObjectFactory.getClassInstance(className);
}
catch (Exception e) {
return bundleResourceLoader.loadClass(className);
}
}
public void destroy() {
if (osgiConfigurationProvider != null) {
osgiConfigurationProvider.destroy();
}
}
@Inject("osgi")
public void setOsgiConfigurationProvider(PackageProvider osgiConfigurationProvider) {
this.osgiConfigurationProvider = (OsgiConfigurationProvider) osgiConfigurationProvider;
}
}
@@ -0,0 +1,376 @@
/*
* $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.config.ConfigurationException;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.URLUtil;
import com.opensymphony.xwork2.util.finder.ResourceFinder;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.opensymphony.xwork2.ActionContext;
import org.apache.commons.lang.xwork.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.StrutsStatics;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.Constants;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleListener;
import org.osgi.framework.BundleEvent;
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 int 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));
//add third party bundles in level 2
List<String> bundleJarsLevel2 = new ArrayList<String>();
bundleJarsLevel2.addAll(getBundlesInDir("bundles/other"));
//start app bundles in level 3
List<String> bundleJarsLevel3 = new ArrayList<String>();
bundleJarsLevel3.addAll(getBundlesInDir("bundles"));
configProps.put(AutoActivator.AUTO_START_PROP + ".1", StringUtils.join(bundleJarsLevel1, " "));
configProps.put(AutoActivator.AUTO_START_PROP + ".2", StringUtils.join(bundleJarsLevel2, " "));
configProps.put(AutoActivator.AUTO_START_PROP + ".3", StringUtils.join(bundleJarsLevel3, " "));
return bundleJarsLevel1.size() + bundleJarsLevel2.size() + bundleJarsLevel3.size();
}
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 bundlerDir = new File(url.toURI());
File[] bundles = bundlerDir.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 {
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();
String 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 {
JarFile jarFile = new JarFile(new File(URLUtil.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();
}
}
@@ -0,0 +1,245 @@
/*
* $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.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.PackageProvider;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.struts2.osgi.loaders.VelocityBundleResourceLoader;
import org.apache.struts2.views.velocity.VelocityManager;
import org.apache.velocity.app.Velocity;
import org.apache.commons.lang.xwork.StringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleListener;
import org.osgi.framework.BundleEvent;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
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 OsgiHost osgiHost;
private BundleContext bundleContext;
private BundleAccessor bundleAccessor;
private boolean bundlesChanged = false;
private ServletContext servletContext;
public void init(Configuration configuration) throws ConfigurationException {
osgiHost = (OsgiHost) servletContext.getAttribute(StrutsOsgiListener.OSGI_HOST);
bundleContext = osgiHost.getBundleContext();
bundleAccessor.setBundleContext(bundleContext);
bundleAccessor.setOsgiHost(osgiHost);
this.configuration = configuration;
}
public synchronized void loadPackages() throws ConfigurationException {
if (LOG.isTraceEnabled())
LOG.trace("Loading packages from XML and Convention on startup");
//init action contect
ActionContext ctx = ActionContext.getContext();
if (ctx == null) {
ctx = new ActionContext(new HashMap());
ActionContext.setContext(ctx);
}
Set<String> bundleNames = new HashSet<String>();
//iterate over the bundles and load packages from them
for (Bundle bundle : osgiHost.getBundles().values()) {
String bundleName = bundle.getSymbolicName();
if (shouldProcessBundle(bundle) && !bundleNames.contains(bundleName)) {
bundleNames.add(bundleName);
//load XML and COnvention config
loadConfigFromBundle(bundle);
}
}
bundlesChanged = false;
bundleContext.addBundleListener(this);
}
/**
* Loads XML config as well as Convention config from a bundle
* Limitation: Constants and Beans are ignored on XML config
*/
protected void loadConfigFromBundle(Bundle bundle) {
String bundleName = bundle.getSymbolicName();
if (LOG.isDebugEnabled())
LOG.debug("Loading packages from bundle [#0]", bundleName);
//init action context
ActionContext ctx = ActionContext.getContext();
if (ctx == null) {
ctx = new ActionContext(new HashMap());
ActionContext.setContext(ctx);
}
try {
//the Convention plugin will use BundleClassLoaderInterface from the ActionContext to find resources
//and load classes
ctx.put(ClassLoaderInterface.CLASS_LOADER_INTERFACE, new BundleClassLoaderInterface());
ctx.put(BundleAccessor.CURRENT_BUNDLE_NAME, bundleName);
if (LOG.isTraceEnabled())
LOG.trace("Loading XML config from bundle [#0]", bundleName);
//XML config
PackageLoader loader = new BundlePackageLoader();
for (PackageConfig pkg : loader.loadPackages(bundle, bundleContext, objectFactory, 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());
PackageProvider conventionPackageProvider = configuration.getContainer().getInstance(PackageProvider.class, "convention.packageProvider");
if (conventionPackageProvider != null) {
if (LOG.isTraceEnabled())
LOG.trace("Loading Convention config from bundle [#0]", bundleName);
conventionPackageProvider.loadPackages();
}
Set<String> packagesAfterLoading = new HashSet(configuration.getPackageConfigNames());
packagesAfterLoading.removeAll(packagesBeforeLoading);
if (!packagesAfterLoading.isEmpty()) {
//add the new packages to the map of bundle -> package
for (String packageName : packagesAfterLoading)
bundleAccessor.addPackageFromBundle(bundle, packageName);
}
if (this.configuration.getRuntimeConfiguration() != null) {
//if there is a runtime config, it meas that this method was called froma bundle start event
//instead of the initial load, in that case, reload the config
this.configuration.rebuildRuntimeConfiguration();
}
} finally {
ctx.put(BundleAccessor.CURRENT_BUNDLE_NAME, null);
ctx.put(ClassLoaderInterface.CLASS_LOADER_INTERFACE, null);
}
}
/**
* Checks for "Struts2-Enabled" header in the bundle
*/
protected boolean shouldProcessBundle(Bundle bundle) {
String strutsEnabled = (String) bundle.getHeaders().get(OsgiHost.OSGI_HEADER_STRUTS_ENABLED);
return "true".equalsIgnoreCase(strutsEnabled);
}
public synchronized boolean needsReload() {
return bundlesChanged;
}
@Inject
public void setObjectFactory(ObjectFactory factory) {
this.objectFactory = factory;
}
@Inject
public void setBundleAccessor(BundleAccessor acc) {
this.bundleAccessor = acc;
}
@Inject
public void setVelocityManager(VelocityManager vm) {
Properties props = new Properties();
props.setProperty("osgi.resource.loader.description", "OSGI bundle loader");
props.setProperty("osgi.resource.loader.class", VelocityBundleResourceLoader.class.getName());
props.setProperty(Velocity.RESOURCE_LOADER, "strutsfile,strutsclass,osgi");
vm.setVelocityProperties(props);
}
@Inject
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void destroy() {
try {
osgiHost.destroy();
} catch (Exception e) {
if (LOG.isErrorEnabled())
LOG.error("Failed to stop OSGi container", e);
}
}
/**
* Listens to bundle event to load/unload config
*/
public void bundleChanged(BundleEvent bundleEvent) {
Bundle bundle = bundleEvent.getBundle();
String bundleName = bundle.getSymbolicName();
if (bundleName != null && shouldProcessBundle(bundle)) {
switch (bundleEvent.getType()) {
case BundleEvent.STARTED:
if (LOG.isTraceEnabled())
LOG.trace("The bundlde [#0] has been activated and will be scanned for struts configuration", bundleName);
loadConfigFromBundle(bundle);
break;
case BundleEvent.STOPPED:
onBundleStopped(bundle);
break;
}
}
}
/**
* This method is called when a bundle is stopped, so the config that is related to it is removed
*
* @param bundle the bundle that stopped
*/
protected void onBundleStopped(Bundle bundle) {
Set<String> packages = bundleAccessor.getPackagesByBundle(bundle);
if (!packages.isEmpty()) {
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)
configuration.removePackageConfig(packageName);
}
}
}
@@ -0,0 +1,45 @@
/*
* $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.osgi.framework.BundleActivator;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
/**
* Implementations of this class start an OSGi container. They must also add the BundleContext to
* the ServletContext under the key OsgiHost.OSGI_BUNDLE_CONTEXT;
*/
public interface OsgiHost {
String OSGI_BUNDLE_CONTEXT = "__struts_osgi_bundle_context";
String OSGI_HEADER_STRUTS_ENABLED = "Struts2-Enabled";
void destroy() throws Exception;
void init(ServletContext servletContext);
Map<String, Bundle> getBundles();
Map<String, Bundle> getActiveBundles();
BundleContext getBundleContext();
}
@@ -0,0 +1,86 @@
/*
* $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 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;
public class OsgiUtil {
private static final Logger LOG = LoggerFactory.getLogger(OsgiUtil.class);
/**
* A bundle is a jar, and a bunble URL will be useless to clients, this method translates
* a URL to a resource inside a bundle from "bundle:something/path" to "jar:file:bundlelocation!/path"
*/
public static URL translateBundleURLToJarURL(URL bundleUrl, Bundle bundle) throws MalformedURLException {
if (bundleUrl != null && "bundle".equalsIgnoreCase(bundleUrl.getProtocol())) {
StringBuilder sb = new StringBuilder("jar:");
sb.append(bundle.getLocation());
sb.append("!");
sb.append(bundleUrl.getFile());
return new URL(sb.toString());
}
return bundleUrl;
}
/**
* Calls getBean() on the passed object using refelection. Used on Spring context
* because they are loaded from bundles (in anothe class loader)
*/
public static Object getBean(Object beanFactory, String beanId) {
try {
Method getBeanMethod = beanFactory.getClass().getMethod("getBean", String.class);
return getBeanMethod.invoke(beanFactory, beanId);
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Unable to call getBean() on object of type [#0], with bean id [#1]", ex, beanFactory.getClass().getName(), beanId);
}
return null;
}
/**
* Calls containsBean on the passed object using refelection. Used on Spring context
* because they are loaded from bundles (in anothe class loader)
*/
public static boolean containsBean(Object beanFactory, String beanId) {
try {
Method getBeanMethod = beanFactory.getClass().getMethod("containsBean", String.class);
return (Boolean) getBeanMethod.invoke(beanFactory, beanId);
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Unable to call containsBean() on object of type [#0], with bean id [#1]", ex, beanFactory.getClass().getName(), beanId);
}
return false;
}
}
@@ -0,0 +1,40 @@
/*
* $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 java.util.List;
import java.util.Map;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.PackageProvider;
import com.opensymphony.xwork2.config.entities.PackageConfig;
/**
* Implementations of this interface can load packages from a Bundle
*/
public interface PackageLoader {
List<PackageConfig> loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory, Map<String, PackageConfig> map) throws ConfigurationException;
}
@@ -0,0 +1,91 @@
/*
* $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.ObjectFactory;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.inject.Inject;
import org.osgi.framework.ServiceReference;
import java.util.Map;
/**
* This Object factory uses the ActionContext(s) published by Spring OSGi
* to lookup beans
*/
public class SpringOsgiObjectFactory extends ObjectFactory {
private final static String SPRING_SERVICE_NAME = "org.springframework.context.ApplicationContext";
private BundleAccessor bundleAccessor;
public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
if (containsBean(className))
return getBean(className);
else {
Class clazz = ClassLoaderUtil.loadClass(className, SpringOsgiObjectFactory.class);
Object object = clazz.newInstance();
if (injectInternal)
injectInternalBeans(object);
return object;
}
}
public Object buildBean(Class clazz, Map<String, Object> extraContext) throws Exception {
return clazz.newInstance();
}
public Class getClassInstance(String className) throws ClassNotFoundException {
return containsBean(className) ? getBean(className).getClass() : ClassLoaderUtil.loadClass(className, SpringOsgiObjectFactory.class);
}
protected Object getBean(String beanName) {
ServiceReference[] refs = bundleAccessor.getAllServiceReferences(SPRING_SERVICE_NAME);
if (refs != null) {
for (ServiceReference ref : refs) {
Object context = bundleAccessor.getService(ref);
if (OsgiUtil.containsBean(context, beanName))
return OsgiUtil.getBean(context, beanName);
}
}
return null;
}
protected boolean containsBean(String beanName) {
ServiceReference[] refs = bundleAccessor.getAllServiceReferences(SPRING_SERVICE_NAME);
if (refs != null) {
for (ServiceReference ref : refs) {
Object context = bundleAccessor.getService(ref);
if (OsgiUtil.containsBean(context, beanName))
return true;
}
}
return false;
}
@Inject
public void setBundleAccessor(BundleAccessor bundleAccessor) {
this.bundleAccessor = bundleAccessor;
}
}
@@ -0,0 +1,34 @@
package org.apache.struts2.osgi;
import org.apache.struts2.StrutsException;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContext;
/**
* ServletContextListener that starts Apache Felix
*/
public class StrutsOsgiListener implements ServletContextListener {
public static final String OSGI_HOST = "__struts_osgi_host";
private FelixOsgiHost osgiHost;
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
osgiHost = new FelixOsgiHost();
servletContext.setAttribute(OSGI_HOST, osgiHost);
try {
osgiHost.init(servletContext);
} catch (Exception e) {
throw new StrutsException("Apache Felix failed to start", e);
}
}
public void contextDestroyed(ServletContextEvent sce) {
try {
osgiHost.destroy();
} catch (Exception e) {
throw new StrutsException("Apache Felix failed to stop", e);
}
}
}
@@ -0,0 +1,31 @@
/*
* $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.interceptor;
import org.osgi.framework.BundleContext;
/**
* Actions implementing this interface will receive an instance of the BundleContext,
* the OsgiInterceptor must be applied to the action.
*/
public interface BundleContextAware {
void setBundleContext(BundleContext bundleContext);
}
@@ -0,0 +1,99 @@
/*
* $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.interceptor;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.ActionInvocation;
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.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.ArrayList;
/**
* 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;
public String intercept(ActionInvocation invocation) throws Exception {
if (bundleContext != null) {
Object action = invocation.getAction();
//inject BundleContext
if (action instanceof BundleContextAware)
((BundleContextAware)action).setBundleContext(bundleContext);
//inject service implementations
if (action instanceof ServiceAware) {
Type[] types = action.getClass().getGenericInterfaces();
if (types != null) {
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
if (parameterizedType.getRawType() instanceof Class) {
Class clazz = (Class) parameterizedType.getRawType();
if (ServiceAware.class.equals(clazz)) {
Class serviceClass = (Class) parameterizedType.getActualTypeArguments()[0];
ServiceReference[] refs = bundleContext.getAllServiceReferences(serviceClass.getName(), null);
//get the services
if (refs != null) {
List services = new ArrayList(refs.length);
for (ServiceReference ref : refs) {
Object service = bundleContext.getService(ref);
//wow, that's a lot of nested ifs
if (service != null)
services.add(service);
}
if (!services.isEmpty())
((ServiceAware)action).setServices(services);
}
}
}
}
}
}
}
} else if (LOG.isWarnEnabled()){
LOG.warn("The OSGi interceptor was not able to find the BundleContext in the ServletContext");
}
return invocation.invoke();
}
@Inject
public void setServletContext(ServletContext servletContext) {
this.bundleContext = (BundleContext) servletContext.getAttribute(OsgiHost.OSGI_BUNDLE_CONTEXT);
}
}
@@ -0,0 +1,32 @@
/*
* $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.interceptor;
import java.util.List;
/**
* Classes implementing this interface, will be injected a list of services
* registered with the type of the parameterized type
* @param <T> The type of the service
*/
public interface ServiceAware<T> {
void setServices(List<T> services);
}
@@ -0,0 +1,40 @@
/*
* $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.loaders;
import java.net.URL;
import org.apache.struts2.osgi.DefaultBundleAccessor;
import freemarker.cache.URLTemplateLoader;
/**
* Finds FreeMarker templates in bundles
*/
public class FreeMarkerBundleResourceLoader extends URLTemplateLoader {
@Override
protected URL getURL(String name) {
return DefaultBundleAccessor.getInstance().loadResource(name);
}
}
@@ -0,0 +1,48 @@
/*
* $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.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;
/**
* Loads static resources from bundles
*
*/
public class StaticContentBundleResourceLoader extends DefaultStaticContentLoader {
private BundleAccessor bundleAccessor;
protected URL findResource(String path) throws IOException {
return bundleAccessor.loadResourceFromAllBundles(path);
}
@Inject
public void setBundleAccessor(BundleAccessor bundleAccessor) {
this.bundleAccessor = bundleAccessor;
}
}
@@ -0,0 +1,51 @@
/*
* $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.loaders;
import java.io.InputStream;
import org.apache.struts2.osgi.DefaultBundleAccessor;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
/**
* Finds Velocity templates in bundles
*/
public class VelocityBundleResourceLoader extends ClasspathResourceLoader {
public synchronized InputStream getResourceStream(String name)
throws ResourceNotFoundException {
if ((name == null) || (name.length() == 0)) {
throw new ResourceNotFoundException("No template name provided");
}
if (name.startsWith("/")) {
name = name.substring(1);
}
try {
return DefaultBundleAccessor.getInstance().loadResourceAsStream(name);
} catch (Exception e) {
throw new ResourceNotFoundException(e.getMessage());
}
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="parent-context-bean" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>classpath*:/spring/*.xml</value>
</list>
</constructor-arg>
</bean>
</beans>
@@ -0,0 +1,25 @@
# 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.
scanning.package.includes = com.opensymphony.xwork2, \
org.apache.commons.lang.xwork, \
org.apache.struts2, \
ognl, \
freemarker, \
org.apache.velocity, \
javax.servlet
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
"http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts order="10">
<constant name="struts.objectFactory" value="osgi" />
<constant name="struts.objectFactory.delegate" value="struts" />
<constant name="struts.freemarker.manager.classname" value="org.apache.struts2.osgi.BundleFreemarkerManager" />
<constant name="struts.staticContentLoader" value="org.apache.struts2.osgi.loaders.StaticContentBundleResourceLoader" />
<constant name="struts.convention.action.includeJars" value="jar:file:.*?/bundles/.*?\.jar(!/)?" />
<bean type="org.apache.struts2.osgi.BundleAccessor" class="org.apache.struts2.osgi.DefaultBundleAccessor" />
<bean type="org.apache.struts2.osgi.PackageLoader" class="org.apache.struts2.osgi.BundlePackageLoader" />
<bean name="osgi" type="com.opensymphony.xwork2.ObjectFactory" class="org.apache.struts2.osgi.DelegatingObjectFactory" />
<bean name="springOsgi" type="com.opensymphony.xwork2.ObjectFactory" class="org.apache.struts2.osgi.SpringOsgiObjectFactory" />
<bean name="osgi" type="com.opensymphony.xwork2.config.PackageProvider" class="org.apache.struts2.osgi.OsgiConfigurationProvider" />
<bean name="osgi" type="com.opensymphony.xwork2.util.finder.ClassLoaderInterface" class="org.apache.struts2.osgi.BundleClassLoaderInterface" />
<package name="osgi-default" extends="struts-default" abstract="yes">
<interceptors>
<interceptor name="osgi" class="org.apache.struts2.osgi.interceptor.OsgiInterceptor" />
<interceptor-stack name="osgiStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="osgi"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="osgiStack"/>
</package>
</struts>
@@ -0,0 +1,35 @@
/*
* $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 junit.framework.TestCase;
public class FelixOsgiHostTest extends TestCase {
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"));
}
}
@@ -0,0 +1,85 @@
package org.apache.struts2.osgi.interceptor;
import org.easymock.EasyMock;
import org.apache.struts2.osgi.OsgiHost;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import javax.servlet.ServletContext;
import com.opensymphony.xwork2.ActionInvocation;
import junit.framework.TestCase;
import java.util.List;
public class OsgiInterceptorTest extends TestCase {
public void testBundleContextAware() throws Exception {
ServletContext servletContext = EasyMock.createStrictMock(ServletContext.class);
BundleContext bundleContext = EasyMock.createStrictMock(BundleContext.class);
ActionInvocation actionInvocation = EasyMock.createStrictMock(ActionInvocation.class);
BundleContextAware bundleContextAware = EasyMock.createStrictMock(BundleContextAware.class);
EasyMock.expect(servletContext.getAttribute(OsgiHost.OSGI_BUNDLE_CONTEXT)).andReturn(bundleContext);
EasyMock.expect(actionInvocation.getAction()).andReturn(bundleContextAware);
bundleContextAware.setBundleContext(bundleContext);
EasyMock.expect(actionInvocation.invoke()).andReturn("");
EasyMock.replay(bundleContextAware);
EasyMock.replay(servletContext);
EasyMock.replay(actionInvocation);
OsgiInterceptor osgiInterceptor = new OsgiInterceptor();
osgiInterceptor.setServletContext(servletContext);
osgiInterceptor.intercept(actionInvocation);
EasyMock.verify(bundleContextAware);
}
public void testBundleContextAwareNegative() throws Exception {
ServletContext servletContext = EasyMock.createStrictMock(ServletContext.class);
ActionInvocation actionInvocation = EasyMock.createStrictMock(ActionInvocation.class);
BundleContextAware bundleContextAware = EasyMock.createStrictMock(BundleContextAware.class);
EasyMock.expect(servletContext.getAttribute(OsgiHost.OSGI_BUNDLE_CONTEXT)).andReturn(null);
EasyMock.expect(actionInvocation.invoke()).andReturn("");
EasyMock.replay(bundleContextAware);
EasyMock.replay(servletContext);
EasyMock.replay(actionInvocation);
OsgiInterceptor osgiInterceptor = new OsgiInterceptor();
osgiInterceptor.setServletContext(servletContext);
osgiInterceptor.intercept(actionInvocation);
EasyMock.verify(bundleContextAware);
}
public void testServiceAware() throws Exception {
ServletContext servletContext = EasyMock.createStrictMock(ServletContext.class);
BundleContext bundleContext = EasyMock.createStrictMock(BundleContext.class);
ActionInvocation actionInvocation = EasyMock.createStrictMock(ActionInvocation.class);
SomeAction someAction = new SomeAction();
//service refs
ServiceReference objectRef = EasyMock.createNiceMock(ServiceReference.class);
Object someObject = new Object();
EasyMock.expect(servletContext.getAttribute(OsgiHost.OSGI_BUNDLE_CONTEXT)).andReturn(bundleContext);
EasyMock.expect(actionInvocation.getAction()).andReturn(someAction);
EasyMock.expect(actionInvocation.invoke()).andReturn("");
EasyMock.expect(bundleContext.getAllServiceReferences(Object.class.getName(), null)).andReturn(new ServiceReference[] {objectRef});
EasyMock.expect(bundleContext.getService(objectRef)).andReturn(someObject);
EasyMock.replay(bundleContext);
EasyMock.replay(servletContext);
EasyMock.replay(actionInvocation);
OsgiInterceptor osgiInterceptor = new OsgiInterceptor();
osgiInterceptor.setServletContext(servletContext);
osgiInterceptor.intercept(actionInvocation);
List<Object> objects = someAction.getServices();
assertNotNull(objects);
assertSame(someObject, objects.get(0));
}
}
@@ -0,0 +1,36 @@
/*
* $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.interceptor;
import java.util.List;
public class SomeAction implements ServiceAware<Object> {
private List<Object> services;
public List<Object> getServices() {
return services;
}
public void setServices(List<Object> services) {
this.services = services;
}
}