/* * Copyright 2014 Harald Wellmann. * * 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.web.extender.impl; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.annotation.HandlesTypes; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebListener; import javax.servlet.annotation.WebServlet; import org.apache.xbean.finder.BundleAnnotationFinder; import org.apache.xbean.osgi.bundle.util.DelegatingBundle; import org.apache.xbean.osgi.bundle.util.equinox.EquinoxBundleClassLoader; import org.apache.xbean.osgi.bundle.util.jar.BundleJarFile; import org.ops.pax.web.spi.ServletContainer; import org.ops.pax.web.spi.ServletContainerInitializerModel; import org.ops.pax.web.spi.WabModel; import org.ops.pax.web.spi.WebAppModel; import org.ops4j.pax.web.descriptor.gen.FullyQualifiedClassType; import org.ops4j.pax.web.descriptor.gen.ListenerType; import org.ops4j.pax.web.descriptor.gen.WebAppType; import org.ops4j.pax.web.extender.impl.desc.WebDescriptorParser; import org.ops4j.pax.web.extender.war.internal.parser.WebFilterAnnotationScanner; import org.ops4j.pax.web.extender.war.internal.parser.WebServletAnnotationScanner; import org.ops4j.pax.web.utils.ClassPathUtil; import org.ops4j.pax.web.utils.FelixBundleClassLoader; import org.ops4j.pax.web.utils.JarExploder; import org.ops4j.spi.SafeServiceLoader; import org.osgi.framework.Bundle; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.osgi.service.packageadmin.PackageAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("deprecation") @Component(immediate = true, service = DeploymentService.class) public class DeploymentService { private static Logger log = LoggerFactory.getLogger(DeploymentService.class); private Bundle extender; private ServletContainer servletContainer; private EventAdmin eventAdmin; private PackageAdmin packageAdmin; private WebDescriptorParser parser = new WebDescriptorParser(); /** * Deploys the given WAB. Must only be called when all dependencies are satisfied. For bean * bundles, the Pax CDI ServletContainerInitializer is added to the web app. * * @param wabContext * context of current WAB */ public void deploy(WabContext wabContext) { WabModel webApp = wabContext.getWabModel(); Bundle bundle = wabContext.getBundle(); String contextPath = wabContext.getConfiguration().getContextPath(); webApp.setContextPath(contextPath); ClassLoader cl = createExtendedClassLoader(bundle); webApp.setClassLoader(cl); postEvent("org/osgi/service/web/DEPLOYING", bundle, contextPath); boolean metadataComplete = false; Enumeration<URL> entries = bundle.findEntries("WEB-INF", "web.xml", false); if (entries != null && entries.hasMoreElements()) { log.debug("found web.xml in {}", bundle); WebAppModel webAppModel = parser.createWebAppModel(entries.nextElement()); webApp.setWebAppModel(webAppModel); WebAppType webAppType = webApp.getWebAppModel().getWebApp(); if (webAppType.isMetadataComplete() != null) { metadataComplete = webAppType.isMetadataComplete(); } } int majorVersion = getMajorVersion(webApp); if (majorVersion >= 3) { servletContainerInitializerScan(bundle, webApp); } else { metadataComplete = true; } if (!metadataComplete) { servletAnnotationScan(bundle, webApp.getWebAppModel()); } webApp.getVirtualHosts().add(wabContext.getConfiguration().getVirtualHost()); if (wabContext.isBeanBundle()) { ServletContainerInitializerModel wsci = new ServletContainerInitializerModel(); wsci.setServletContainerInitializer(wabContext.getBeanBundleInitializer()); webApp.getServletContainerInitializers().add(wsci); } log.info("deploying {}", contextPath); explodeArchive(webApp); servletContainer.deploy(webApp); wabContext.setDeployed(true); postEvent("org/osgi/service/web/DEPLOYED", bundle, contextPath); } private void explodeArchive(WabModel webApp) { Bundle bundle = webApp.getBundle(); try { BundleJarFile jarFile = new BundleJarFile(bundle); JarExploder exploder = new JarExploder(); File explodedDir = bundle.getDataFile("exploded"); explodedDir.mkdirs(); exploder.processJarFile(jarFile, explodedDir.getAbsolutePath()); webApp.setExplodedDir(explodedDir); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private int getMajorVersion(WabModel wabModel) { WebAppType webApp = wabModel.getWebAppModel().getWebApp(); if (webApp == null) { return 3; } String version = webApp.getVersion(); String[] parts = version.split("\\."); return Integer.parseInt(parts[0]); } @SuppressWarnings("unchecked") private void servletContainerInitializerScan(Bundle bundle, WabModel webApp) { log.debug("scanning for ServletContainerInitializers"); ClassLoader cl = webApp.getClassLoader(); SafeServiceLoader safeServiceLoader = new SafeServiceLoader(cl); List<ServletContainerInitializer> containerInitializers = safeServiceLoader .load("javax.servlet.ServletContainerInitializer"); for (ServletContainerInitializer sci : containerInitializers) { ServletContainerInitializerModel sciModel = new ServletContainerInitializerModel(); sciModel.setServletContainerInitializer(sci); try { Class<HandlesTypes> loadClass = (Class<HandlesTypes>) cl .loadClass("javax.servlet.annotation.HandlesTypes"); HandlesTypes handlesTypes = loadClass.cast(sci.getClass().getAnnotation(loadClass)); log.debug("Found HandlesTypes {}", handlesTypes); if (handlesTypes != null) { // add annotated classes to service Class<?>[] classes = handlesTypes.value(); sciModel.setClasses(classes); } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } webApp.getServletContainerInitializers().add(sciModel); } } private void servletAnnotationScan(final Bundle bundle, final WebAppModel webApp) { log.debug("metadata-complete is either false or not set"); log.debug("scanning for annotated classes"); BundleAnnotationFinder baf = null; try { baf = new BundleAnnotationFinder(packageAdmin, bundle); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return; } Set<Class<?>> webServletClasses = new LinkedHashSet<Class<?>>( baf.findAnnotatedClasses(WebServlet.class)); Set<Class<?>> webFilterClasses = new LinkedHashSet<Class<?>>( baf.findAnnotatedClasses(WebFilter.class)); Set<Class<?>> webListenerClasses = new LinkedHashSet<Class<?>>( baf.findAnnotatedClasses(WebListener.class)); for (Class<?> webServletClass : webServletClasses) { log.debug("found WebServlet annotation on class: {}", webServletClass); WebServletAnnotationScanner annonScanner = new WebServletAnnotationScanner(bundle, webServletClass.getCanonicalName()); annonScanner.scan(webApp); } for (Class<?> webFilterClass : webFilterClasses) { log.debug("found WebFilter annotation on class: {}", webFilterClass); WebFilterAnnotationScanner filterScanner = new WebFilterAnnotationScanner(bundle, webFilterClass.getCanonicalName()); filterScanner.scan(webApp); } for (Class<?> webListenerClass : webListenerClasses) { log.debug("found WebListener annotation on class: {}", webListenerClass); addWebListener(webApp, webListenerClass.getCanonicalName()); } log.debug("class scanning done"); } private static void addWebListener(final WebAppModel webApp, String clazz) { ListenerType listener = new ListenerType(); FullyQualifiedClassType klass = new FullyQualifiedClassType(); klass.setValue(clazz); listener.setListenerClass(klass); webApp.getListeners().add(listener); } public void undeploy(WabContext wabContext) { WabModel webApp = wabContext.getWabModel(); String contextPath = webApp.getContextPath(); Bundle bundle = wabContext.getBundle(); postEvent("org/osgi/service/web/UNDEPLOYING", bundle, contextPath); log.info("undeploying {}", contextPath); servletContainer.undeploy(webApp); deleteRecursively(webApp.getExplodedDir()); wabContext.setWabModel(null); ; postEvent("org/osgi/service/web/UNDEPLOYED", bundle, contextPath); wabContext.setDeployed(false); } private void deleteRecursively(File explodedDir) { Path directory = explodedDir.toPath(); try { Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Posts a deployment event with the given properties. * * @param topic * event topic * @param bundle * current bundle * @param contextPath * web context path */ private void postEvent(String topic, Bundle bundle, String contextPath) { Map<String, Object> props = buildEventProperties(bundle, contextPath); Event event = new Event(topic, props); eventAdmin.postEvent(event); } /** * Builds properties for a deployment event. * * @param bundle * current bundle * @param contextPath * web context path * @return property map */ private Map<String, Object> buildEventProperties(Bundle bundle, String contextPath) { Map<String, Object> props = new HashMap<>(); props.put("bundle.symbolicName", bundle.getSymbolicName()); props.put("bundle.id", bundle.getBundleId()); props.put("bundle.version", bundle.getVersion()); props.put("bundle", bundle); props.put("context.path", contextPath); props.put("timestamp", System.currentTimeMillis()); props.put("extender.bundle.symbolicName", extender.getSymbolicName()); props.put("extender.bundle.id", extender.getBundleId()); props.put("extender.bundle.version", extender.getVersion()); props.put("extender.bundle", extender); return props; } /** * Creates the extended classloader for the current WAB. Since JSF cannot work with bundle: URLs * and since OSGi has no standard API for converting these URLs to local URLs, we use * framework-specific approaches for Equinox and Felix. * * @param bundle * current web bundle * @return extended class loader */ private ClassLoader createExtendedClassLoader(Bundle bundle) { Set<Bundle> bundleSet = new HashSet<>(); bundleSet = ClassPathUtil.getBundlesInClassSpace(bundle, bundleSet); List<Bundle> bundles = new ArrayList<>(); bundles.add(bundle); bundles.addAll(bundleSet); String vendor = extender.getBundleContext().getProperty("org.osgi.framework.vendor"); ClassLoader cl; if ("Eclipse".equals(vendor)) { cl = new EquinoxBundleClassLoader(new DelegatingBundle(bundles), true, true); } // TODO don't assume that "not Equinox" is equivalent to "Felix" else { cl = new FelixBundleClassLoader(bundles); } log.debug("extended classloader: {}", cl); return cl; } public Bundle getExtender() { return extender; } public void setExtender(Bundle extender) { this.extender = extender; } @Reference public void setServletContainer(ServletContainer servletContainer) { this.servletContainer = servletContainer; } @Reference public void setEventAdmin(EventAdmin eventAdmin) { this.eventAdmin = eventAdmin; } @Reference public void setPackageAdmin(PackageAdmin packageAdmin) { this.packageAdmin = packageAdmin; } }