diff --git a/plugins/osgi/pom.xml b/plugins/osgi/pom.xml new file mode 100644 index 000000000..0d8770924 --- /dev/null +++ b/plugins/osgi/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + org.apache.struts + struts2-plugins + 2.0.9 + + org.apache.struts + struts2-osgi-plugin + jar + 1.0.0-SNAPSHOT + Struts 2 OSGI Plugin + + + scm:svn:http://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-osgi-plugin/ + scm:svn:https://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2-osgi-plugin/ + http://svn.apache.org/viewcvs.cgi/struts/sandbox/trunk/struts2-osgi-plugin/ + + + + + + true + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + + org.apache.felix + org.apache.felix.main + 1.4.1 + + + + org.apache.struts + struts2-core + 2.1.7-SNAPSHOT + + + + org.apache.velocity + velocity + 1.5 + + + + org.apache.velocity + velocity-tools + 1.3 + + + javax.servlet + servlet-api + + + struts + struts + + + + + + commons-digester + commons-digester + 2.0 + + + + junit + junit + test + 3.8.1 + + + + org.easymock + easymock + test + 2.4 + + + diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleAccessor.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleAccessor.java new file mode 100644 index 000000000..6d48a42e3 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleAccessor.java @@ -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 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); +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleClassLoaderInterface.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleClassLoaderInterface.java new file mode 100644 index 000000000..537176c6b --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleClassLoaderInterface.java @@ -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 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; + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleFreemarkerManager.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleFreemarkerManager.java new file mode 100644 index 000000000..e7617df4f --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundleFreemarkerManager.java @@ -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() + }); + } + +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundlePackageLoader.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundlePackageLoader.java new file mode 100644 index 000000000..620eccff4 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/BundlePackageLoader.java @@ -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 loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory, Map 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 list = new ArrayList(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 getConfigurationUrls(String fileName) throws IOException { + Enumeration e = bundle.getResources("struts.xml"); + return e.hasMoreElements() ? new EnumeratorIterator(e) : null; + } + } + + static class EnumeratorIterator implements Iterator { + Enumeration e = null; + + public EnumeratorIterator(Enumeration e) { + this.e = e; + } + + public boolean hasNext() { + return e.hasMoreElements(); + } + + public E next() { + return e.nextElement(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/DefaultBundleAccessor.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/DefaultBundleAccessor.java new file mode 100644 index 000000000..c3c476f39 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/DefaultBundleAccessor.java @@ -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 packageToBundle = new HashMap(); + private Map> packagesByBundle = new HashMap>(); + 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 pkgs = packagesByBundle.get(bundle); + if (pkgs == null) { + pkgs = new HashSet(); + 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 loadResources(String name) throws IOException { + return loadResources(name, false); + } + + public List loadResources(String name, boolean translate) throws IOException { + Bundle bundle = getCurrentBundle(); + if (bundle != null) { + List resources = new ArrayList(); + 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 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 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; + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/DelegatingObjectFactory.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/DelegatingObjectFactory.java new file mode 100644 index 000000000..d71686082 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/DelegatingObjectFactory.java @@ -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 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; + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/FelixOsgiHost.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/FelixOsgiHost.java new file mode 100644 index 000000000..532053485 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/FelixOsgiHost.java @@ -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 + *
+ * Servlet config params: + *

struts.osgi.clearBundleCache: Defaults to "true" delete installed bundles when the comntainer starts

+ *

struts.osgi.logLevel: Defaults to "1". Felix log level. 1 = error, 2 = warning, 3 = information, and 4 = debug

+ *

struts.osgi.runLevel: Defaults to "3". Run level to start the container.

+ */ +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 list = new ArrayList(); + 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 bundleJarsLevel1 = new ArrayList(); + bundleJarsLevel1.add(getJarUrl(ShellService.class)); + bundleJarsLevel1.add(getJarUrl(ServiceTracker.class)); + + //add third party bundles in level 2 + List bundleJarsLevel2 = new ArrayList(); + bundleJarsLevel2.addAll(getBundlesInDir("bundles/other")); + + //start app bundles in level 3 + List bundleJarsLevel3 = new ArrayList(); + 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 getBundlesInDir(String dir) { + List bundleJars = new ArrayList(); + 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 exportedPackages = new ArrayList(); + //build a list of subpackages + for (String rootPackage : rootPackages) { + try { + Map> subpackagesMap = finder.findPackagesMap(StringUtils.replace(rootPackage.trim(), ".", "/")); + for (Map.Entry> entry : subpackagesMap.entrySet()) { + URL url = entry.getKey(); + Set 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 parts = new ArrayList(); + 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 getBundles() { + Map bundles = new HashMap(); + for (Bundle bundle : felix.getBundleContext().getBundles()) { + bundles.put(bundle.getSymbolicName(), bundle); + } + + return Collections.unmodifiableMap(bundles); + } + + public Map getActiveBundles() { + Map bundles = new HashMap(); + 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(); + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiConfigurationProvider.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiConfigurationProvider.java new file mode 100644 index 000000000..13b000adf --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiConfigurationProvider.java @@ -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 bundleNames = new HashSet(); + + //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 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 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 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); + } + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiHost.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiHost.java new file mode 100644 index 000000000..edda076c6 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiHost.java @@ -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 getBundles(); + Map getActiveBundles(); + BundleContext getBundleContext(); +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiUtil.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiUtil.java new file mode 100644 index 000000000..53aa03acd --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiUtil.java @@ -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; + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/PackageLoader.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/PackageLoader.java new file mode 100644 index 000000000..e1d91b40f --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/PackageLoader.java @@ -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 loadPackages(Bundle bundle, BundleContext bundleContext, ObjectFactory objectFactory, Map map) throws ConfigurationException; +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/SpringOsgiObjectFactory.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/SpringOsgiObjectFactory.java new file mode 100644 index 000000000..4e595764e --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/SpringOsgiObjectFactory.java @@ -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 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 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; + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/StrutsOsgiListener.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/StrutsOsgiListener.java new file mode 100644 index 000000000..ec1c322ec --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/StrutsOsgiListener.java @@ -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); + } + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/BundleContextAware.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/BundleContextAware.java new file mode 100644 index 000000000..2ea2a7ab8 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/BundleContextAware.java @@ -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); +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/OsgiInterceptor.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/OsgiInterceptor.java new file mode 100644 index 000000000..7589498ed --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/OsgiInterceptor.java @@ -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, this interceptor will call setService(List) + */ +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); + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/ServiceAware.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/ServiceAware.java new file mode 100644 index 000000000..e7a549ea6 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/interceptor/ServiceAware.java @@ -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 The type of the service + */ +public interface ServiceAware { + void setServices(List services); +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/FreeMarkerBundleResourceLoader.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/FreeMarkerBundleResourceLoader.java new file mode 100644 index 000000000..3638cd04d --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/FreeMarkerBundleResourceLoader.java @@ -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); + } + +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/StaticContentBundleResourceLoader.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/StaticContentBundleResourceLoader.java new file mode 100644 index 000000000..8cf11f58f --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/StaticContentBundleResourceLoader.java @@ -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; + } +} diff --git a/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/VelocityBundleResourceLoader.java b/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/VelocityBundleResourceLoader.java new file mode 100644 index 000000000..20ddd5313 --- /dev/null +++ b/plugins/osgi/src/main/java/org/apache/struts2/osgi/loaders/VelocityBundleResourceLoader.java @@ -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()); + } + } +} diff --git a/plugins/osgi/src/main/resources/beanRefContext.xml b/plugins/osgi/src/main/resources/beanRefContext.xml new file mode 100644 index 000000000..4b9b1615d --- /dev/null +++ b/plugins/osgi/src/main/resources/beanRefContext.xml @@ -0,0 +1,11 @@ + + + + + + + classpath*:/spring/*.xml + + + + diff --git a/plugins/osgi/src/main/resources/struts-osgi.properties b/plugins/osgi/src/main/resources/struts-osgi.properties new file mode 100644 index 000000000..f19215128 --- /dev/null +++ b/plugins/osgi/src/main/resources/struts-osgi.properties @@ -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 + diff --git a/plugins/osgi/src/main/resources/struts-plugin.xml b/plugins/osgi/src/main/resources/struts-plugin.xml new file mode 100644 index 000000000..d628bb8b4 --- /dev/null +++ b/plugins/osgi/src/main/resources/struts-plugin.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/osgi/src/test/java/org/apache/struts2/osgi/FelixOsgiHostTest.java b/plugins/osgi/src/test/java/org/apache/struts2/osgi/FelixOsgiHostTest.java new file mode 100644 index 000000000..7024167ed --- /dev/null +++ b/plugins/osgi/src/test/java/org/apache/struts2/osgi/FelixOsgiHostTest.java @@ -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")); + } +} diff --git a/plugins/osgi/src/test/java/org/apache/struts2/osgi/interceptor/OsgiInterceptorTest.java b/plugins/osgi/src/test/java/org/apache/struts2/osgi/interceptor/OsgiInterceptorTest.java new file mode 100644 index 000000000..4cca06a41 --- /dev/null +++ b/plugins/osgi/src/test/java/org/apache/struts2/osgi/interceptor/OsgiInterceptorTest.java @@ -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 objects = someAction.getServices(); + assertNotNull(objects); + assertSame(someObject, objects.get(0)); + } +} diff --git a/plugins/osgi/src/test/java/org/apache/struts2/osgi/interceptor/SomeAction.java b/plugins/osgi/src/test/java/org/apache/struts2/osgi/interceptor/SomeAction.java new file mode 100644 index 000000000..4355b526f --- /dev/null +++ b/plugins/osgi/src/test/java/org/apache/struts2/osgi/interceptor/SomeAction.java @@ -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 { + private List services; + + + public List getServices() { + return services; + } + + public void setServices(List services) { + this.services = services; + } +}