/** * Copyright OPS4J * * Licensed 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.ops4j.pax.wicket.internal; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.Filter; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.apache.wicket.IPageFactory; import org.apache.wicket.protocol.http.IWebApplicationFactory; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.protocol.http.WicketFilter; import org.ops4j.pax.wicket.api.Constants; import org.ops4j.pax.wicket.api.SuperFilter; import org.ops4j.pax.wicket.api.SuperFilters; import org.ops4j.pax.wicket.api.WebApplicationFactory; import org.ops4j.pax.wicket.internal.filter.FilterDelegator; import org.ops4j.pax.wicket.internal.injection.ComponentInstantiationListenerFacade; import org.ops4j.pax.wicket.spi.support.DelegatingComponentInstanciationListener; import org.ops4j.pax.wicket.util.serialization.PaxWicketSerializer; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An internal wrapper for the {@link org.ops4j.pax.wicket.api.WebApplicationFactory} exported by clients who want to register an application. * This class adds all the logic to extract the required properties from the osgi service and wrapping the created * application factory with the classloading, injection and other tricks required to run the application. * * @author nmw * @version $Id: $Id */ public class PaxWicketApplicationFactory implements IWebApplicationFactory { private static final Logger LOG = LoggerFactory.getLogger(PaxWicketApplicationFactory.class); private final BundleContext bundleContext; private final WebApplicationFactory<? extends WebApplication> webApplicationFactory; private final String applicationName; private final String mountPoint; private final Map<String, String> contextParams; private final File tmpDir; private final FilterDelegator filterDelegator; private final List<SuperFilter> superFilterList = new ArrayList<SuperFilter>(0); private Class<? extends WicketFilter> wicketFilterClass = WicketFilter.class; /** * <p>createPaxWicketApplicationFactory.</p> * * @param bundleContext a {@link org.osgi.framework.BundleContext} object. * @param webApplicationFactory a {@link org.ops4j.pax.wicket.api.WebApplicationFactory} object. * @param reference a {@link org.osgi.framework.ServiceReference} object. * @return a {@link org.ops4j.pax.wicket.internal.PaxWicketApplicationFactory} object. */ @SuppressWarnings("unchecked") public static PaxWicketApplicationFactory createPaxWicketApplicationFactory( BundleContext bundleContext, WebApplicationFactory<?> webApplicationFactory, ServiceReference<WebApplicationFactory<?>> reference) { File tmpDir = retrieveTmpFile(bundleContext); tmpDir.mkdirs(); String mountPoint = (String) reference.getProperty(Constants.MOUNTPOINT); String applicationName = (String) reference.getProperty(Constants.APPLICATION_NAME); Map<String, String> contextParams = (Map<String, String>) reference.getProperty(Constants.CONTEXT_PARAMS); if (contextParams == null) { contextParams = new HashMap<String, String>(); } if (!contextParams.containsKey(WicketFilter.FILTER_MAPPING_PARAM)) { contextParams.put(WicketFilter.FILTER_MAPPING_PARAM, "/" + mountPoint + "/*"); } FilterDelegator filterDelegator = new FilterDelegator(reference.getBundle().getBundleContext(), applicationName); PaxWicketApplicationFactory factory = new PaxWicketApplicationFactory(bundleContext, webApplicationFactory, applicationName, mountPoint, contextParams, tmpDir, filterDelegator); return factory; } private static File retrieveTmpFile(BundleContext bundleContext) { String property = System.getProperty("java.io.tmpdir"); if (property == null) { throw new IllegalStateException("Platform needs file system access to work correctly."); } return new File(property); } /** * <p>getFilterClass.</p> * * @return the concrete wicket filter class we will use as a base to intercept */ public Class<? extends WicketFilter> getFilterClass() { return wicketFilterClass; } private PaxWicketApplicationFactory(BundleContext bundleContext, WebApplicationFactory<? extends WebApplication> webApplicationFactory, String applicationName, String mountPoint, Map<String, String> contextParams, File tmpDir, FilterDelegator filterDelegator) { this.bundleContext = bundleContext; this.webApplicationFactory = webApplicationFactory; this.applicationName = applicationName; this.mountPoint = mountPoint; this.contextParams = contextParams; this.tmpDir = tmpDir; this.filterDelegator = filterDelegator; Class<?> factoryClass = webApplicationFactory.getClass(); SuperFilters superFilters = factoryClass.getAnnotation(SuperFilters.class); LOG.info("Scan for superfilter at class {}...", factoryClass); if (superFilters != null) { LOG.info("Found @SuperFilters annotation at WebApplicationFactory: {}", factoryClass); for (SuperFilter superFilter : superFilters.filters()) { addToSuperFilterList(superFilter); } } else { SuperFilter superFilter = factoryClass.getAnnotation(SuperFilter.class); if (superFilter != null) { LOG.info("Found @SuperFilter annotation at WebApplicationFactory: {}", factoryClass); addToSuperFilterList(superFilter); } } } @SuppressWarnings("unchecked") private void addToSuperFilterList(SuperFilter superFilter) { Class<? extends Filter> filter = superFilter.filter(); if (WicketFilter.class.isAssignableFrom(filter)) { LOG.info("Using special WicketFilter from {}", filter); wicketFilterClass = (Class<? extends WicketFilter>) filter; } else { superFilterList.add(superFilter); } } /** * <p>Getter for the field <code>superFilterList</code>.</p> * * @return the current value of superFilterList */ public List<SuperFilter> getSuperFilterList() { return Collections.unmodifiableList(superFilterList); } /** * <p>isValidFactory.</p> * * @return a boolean. */ public boolean isValidFactory() { return applicationName != null && mountPoint != null; } /** {@inheritDoc} */ public WebApplication createApplication(WicketFilter filter) { return createFromFactory(webApplicationFactory); } private <T extends WebApplication> T createFromFactory(final WebApplicationFactory<T> factory) { final Class<T> applicationClass = factory.getWebApplicationClass(); final ClassLoader factoryClassLoader = factory.getClass().getClassLoader(); final ClassLoader applicationClassClassLoader = applicationClass.getClassLoader(); Enhancer e = new Enhancer(); e.setClassLoader(new ClassLoader(PaxWicketApplicationFactory.class.getClassLoader()) { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { return factoryClassLoader.loadClass(name); } catch (ClassNotFoundException cnf1) { LOG.debug("{} not found in WebApplicationFactory classloader ({}), try WebApplicationClass...", name, factory.getClass() .getName(), cnf1); try { return applicationClassClassLoader.loadClass(name); } catch (ClassNotFoundException cnf2) { LOG.debug("{} not found in WebApplicationClass classloader ({})", name, applicationClass .getClass().getName(), cnf2); throw new ClassNotFoundException( name + " was neither found in WebApplicationFactory nor WebApplicationClass classloader, enable DEBUG loglevel on " + PaxWicketApplicationFactory.class + " to get more details"); } } } }); e.setSuperclass(applicationClass); e.setCallback(new WebApplicationWrapper()); @SuppressWarnings("unchecked") T instance = (T) e.create(); factory.onInstantiation(instance); return instance; } private class WebApplicationWrapper implements MethodInterceptor { private PaxWicketPageFactory pageFactory; private DelegatingClassResolver delegatingClassResolver; private DelegatingComponentInstanciationListener delegatingComponentInstanciationListener; private PageMounterTracker mounterTracker; public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if (isFinalizeMethod(method)) { // swallow finalize call return null; } else if (isInitMethod(method)) { handleInit((WebApplication) object); } else if (isNewPageFactory(method)) { return handleNewPageFactory(); } else if (isOnDestoryMethod(method)) { handleOnDestroy(); } method.setAccessible(true); return methodProxy.invokeSuper(object, args); } /** * A helper method to verify method signatures. * * @param method Method to check. * @param name Expected name. * @param returnType Expected return type. * @param parameterTypes Parameters for method. * @return True if all criteria matched. */ private boolean checkSignature(Method method, String name, Class<?> returnType, Class<?>... parameterTypes) { if (method.getName().equals(name) && method.getReturnType() == returnType) { return Arrays.equals(method.getParameterTypes(), parameterTypes); } return false; } /** * Checks if the method is derived from Object.finalize() * * @param method method being tested * @return true if the method is defined from Object.finalize(), false otherwise */ private boolean isFinalizeMethod(Method method) { return checkSignature(method, "finalize", void.class); } private boolean isInitMethod(Method method) { return checkSignature(method, "init", void.class); } private boolean isNewPageFactory(Method method) { return checkSignature(method, "newPageFactory", IPageFactory.class); } private boolean isOnDestoryMethod(Method method) { return checkSignature(method, "onDestroy", void.class); } private void handleInit(WebApplication application) { // application.initApplication(); delegatingClassResolver = new DelegatingClassResolver(bundleContext, applicationName); delegatingClassResolver.intialize(); delegatingComponentInstanciationListener = new DelegatingComponentInstanciationListener(bundleContext, applicationName); delegatingComponentInstanciationListener.intialize(); application.getFrameworkSettings().setSerializer(new PaxWicketSerializer(getApplicationName())); application.getComponentInstantiationListeners().add(new ComponentInstantiationListenerFacade( delegatingComponentInstanciationListener)); application.getApplicationSettings().setClassResolver(delegatingClassResolver); mounterTracker = new PageMounterTracker(bundleContext, application, getApplicationName()); mounterTracker.open(); filterDelegator.start(); } private IPageFactory handleNewPageFactory() { if (pageFactory == null) { pageFactory = new PaxWicketPageFactory(bundleContext, applicationName); pageFactory.initialize(); } return pageFactory; } private void handleOnDestroy() { PaxWicketPageFactory old = pageFactory; pageFactory = null; old.dispose(); delegatingClassResolver.dispose(); delegatingComponentInstanciationListener.dispose(); mounterTracker.close(); filterDelegator.stop(); } } /** * <p>Getter for the field <code>bundleContext</code>.</p> * * @return a {@link org.osgi.framework.BundleContext} object. */ public BundleContext getBundleContext() { return bundleContext; } /** * <p>Getter for the field <code>webApplicationFactory</code>.</p> * * @return a {@link org.ops4j.pax.wicket.api.WebApplicationFactory} object. */ public WebApplicationFactory<? extends WebApplication> getWebApplicationFactory() { return webApplicationFactory; } /** * <p>Getter for the field <code>applicationName</code>.</p> * * @return a {@link java.lang.String} object. */ public String getApplicationName() { return applicationName; } /** * <p>Getter for the field <code>mountPoint</code>.</p> * * @return a {@link java.lang.String} object. */ public String getMountPoint() { return mountPoint; } /** * <p>Getter for the field <code>contextParams</code>.</p> * * @return a {@link java.util.Map} object. */ public Map<String, String> getContextParams() { return contextParams; } /** * <p>Getter for the field <code>tmpDir</code>.</p> * * @return a {@link java.io.File} object. */ public File getTmpDir() { return tmpDir; } /** * <p>Getter for the field <code>filterDelegator</code>.</p> * * @return a {@link org.ops4j.pax.wicket.internal.filter.FilterDelegator} object. */ public FilterDelegator getFilterDelegator() { return filterDelegator; } /** {@inheritDoc} */ public void destroy(WicketFilter filter) { } }