/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.deployment; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.management.Notification; import org.jboss.bootstrap.spi.ServerConfig; import org.jboss.bootstrap.spi.util.ServerConfigUtil; import org.jboss.mx.util.MBeanProxyExt; import org.jboss.system.ServiceMBeanSupport; import org.jboss.system.server.ServerConfigLocator; import org.jboss.util.file.JarUtils; import org.jboss.util.stream.Streams; /** * An abstract {@link SubDeployer}. * * Provides registration with {@link MainDeployer} as well as * implementations of init, create, start, stop and destroy that * generate JMX notifications on completion of the method. * * @version <tt>$Revision: 81033 $</tt> * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a> * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a> */ public abstract class SubDeployerSupport extends ServiceMBeanSupport implements SubDeployerExt, SubDeployerExtMBean { /** * Holds the native library <em>suffix</em> for this system. * * Determined by examining the result of System.mapLibraryName(specialToken). * The special token defaults to "XxX", but can be changed by setting the * system property: <tt>org.jboss.deployment.SubDeployerSupport.nativeLibToken</tt>. */ protected static final String nativeSuffix; /** * Holds the native library <em>prefix</em> for this system. * * @see #nativeSuffix */ protected static final String nativePrefix; /** A proxy to the MainDeployer. */ protected MainDeployerMBean mainDeployer; /** The temporary directory into which deployments are unpacked */ protected File tempDeployDir; /** The list of enhancedSuffixes for this subdeployer */ protected String[] enhancedSuffixes; /** The suffixes of interest to this subdeployer */ protected String[] suffixes; /** The relative order of this subdeployer - not really used */ protected int relativeOrder = -1; /** The temporary directory where native libs are unpacked. */ private File tempNativeDir; /** Whether to load native libraries */ private boolean loadNative = false; /** * The <code>createService</code> method is one of the ServiceMBean lifecyle operations. * (no jmx tag needed from superinterface) * * @exception Exception if an error occurs */ protected void createService() throws Exception { // get the temporary directories to use ServerConfig config = ServerConfigLocator.locate(); tempNativeDir = config.getServerNativeDir(); tempDeployDir = config.getServerTempDeployDir(); loadNative = ServerConfigUtil.isLoadNative(); // Setup the proxy to mainDeployer mainDeployer = (MainDeployerMBean) MBeanProxyExt.create(MainDeployerMBean.class, MainDeployerMBean.OBJECT_NAME, server); } /** * Performs SubDeployer registration. */ protected void startService() throws Exception { // Register with the main deployer mainDeployer.addDeployer(this); } /** * Performs SubDeployer deregistration. */ protected void stopService() throws Exception { // Unregister with the main deployer mainDeployer.removeDeployer(this); } /** * Clean up. */ protected void destroyService() throws Exception { // Help the GC mainDeployer = null; tempNativeDir = null; } /** * Set an array of suffixes of interest to this subdeployer. * No need to register twice suffixes that may refer to * unpacked deployments (e.g. .sar, .sar/). * * @param suffixes array of suffix strings */ protected void setSuffixes(String[] suffixes) { this.suffixes = suffixes; } /** * Set the relative order of the specified suffixes * all to the same value. * * @param relativeOrder the relative order of the specified suffixes */ protected void setRelativeOrder(int relativeOrder) { this.relativeOrder = relativeOrder; } /** * Set the enhanced suffixes list for this deployer, * causing also the supported suffixes list to be updated. * * Each enhanced suffix entries has the form: * * [order:]suffix * * No need to register twice suffixes that may refer to * unpacked deployments (e.g. .sar, .sar/). * * @param enhancedSuffixes */ public void setEnhancedSuffixes(String[] enhancedSuffixes) { if (enhancedSuffixes != null) { int len = enhancedSuffixes.length; suffixes = new String[len]; for (int i = 0; i < len; i++) { // parse each enhancedSuffix SuffixOrderHelper.EnhancedSuffix e = new SuffixOrderHelper.EnhancedSuffix(enhancedSuffixes[i]); suffixes[i] = e.suffix; } } this.enhancedSuffixes = enhancedSuffixes; } /** * Get an array of enhancedSuffixes * * @return array of enhanced suffix strings */ public String[] getEnhancedSuffixes() { return enhancedSuffixes; } /** * Get an array of suffixes of interest to this subdeployer * * @return array of suffix strings */ public String[] getSuffixes() { return suffixes; } /** * Get the relative order of the specified suffixes * * @return the relative order of the specified suffixes */ public int getRelativeOrder() { return relativeOrder; } /** * A default implementation that uses the suffixes registered * through either setSuffixes() or setEnhancedSuffixes(), to * decide if a module is deployable by this deployer. * * If (according to DeploymentInfo) the deployment refers to * a directory, but not an xml or script deployment, then * the deployment suffix will be checked also against the * registered suffixes + "/". * * @param sdi the DeploymentInfo to check * @return whether the deployer can handle the deployment */ public boolean accepts(DeploymentInfo sdi) { String[] acceptedSuffixes = getSuffixes(); if (acceptedSuffixes == null) { return false; } else { String urlPath = sdi.url.getPath(); String shortName = sdi.shortName; boolean checkDir = sdi.isDirectory && !(sdi.isXML || sdi.isScript); for (int i = 0; i < acceptedSuffixes.length; i++) { // First check the urlPath the might end in "/" // then check the shortName where "/" is removed if (urlPath.endsWith(acceptedSuffixes[i]) || (checkDir && shortName.endsWith(acceptedSuffixes[i]))) { return true; } } return false; } } /** * Sub-classes should override this method to provide * custom 'init' logic. * * <p>This method calls the processNestedDeployments(di) method and then * issues a JMX notification of type SubDeployer.INIT_NOTIFICATION. * This behaviour can overridden by concrete sub-classes. If further * initialization needs to be done, and you wish to preserve the * functionality, be sure to call super.init(di) at the end of your * implementation. */ public void init(DeploymentInfo di) throws DeploymentException { processNestedDeployments(di); emitNotification(SubDeployer.INIT_NOTIFICATION, di); } /** * Sub-classes should override this method to provide * custom 'create' logic. * * This method issues a JMX notification of type SubDeployer.CREATE_NOTIFICATION. */ public void create(DeploymentInfo di) throws DeploymentException { emitNotification(SubDeployer.CREATE_NOTIFICATION, di); } /** * Sub-classes should override this method to provide * custom 'start' logic. * * This method issues a JMX notification of type SubDeployer.START_NOTIFICATION. */ public void start(DeploymentInfo di) throws DeploymentException { emitNotification(SubDeployer.START_NOTIFICATION, di); } /** * Sub-classes should override this method to provide * custom 'stop' logic. * * This method issues a JMX notification of type SubDeployer.START_NOTIFICATION. */ public void stop(DeploymentInfo di) throws DeploymentException { emitNotification(SubDeployer.STOP_NOTIFICATION, di); } /** * Sub-classes should override this method to provide * custom 'destroy' logic. * * This method issues a JMX notification of type SubDeployer.DESTROY_NOTIFICATION. */ public void destroy(DeploymentInfo di) throws DeploymentException { emitNotification(SubDeployer.DESTROY_NOTIFICATION, di); } /** * Simple helper to emit a subdeployer notification containing DeploymentInfo */ protected void emitNotification(String type, DeploymentInfo di) { Notification notification = new Notification(type, this, getNextNotificationSequenceNumber()); notification.setUserData(di); sendNotification(notification); } /** * The <code>processNestedDeployments</code> method searches for any nested and * deployable elements. Only Directories and Zipped archives are processed, * and those are delegated to the addDeployableFiles and addDeployableJar * methods respectively. This method can be overridden for alternate * behaviour. */ protected void processNestedDeployments(DeploymentInfo di) throws DeploymentException { log.debug("looking for nested deployments in : " + di.url); if (di.isXML) { // no nested archives in an xml file return; } if (di.isDirectory) { File f = new File(di.url.getFile()); if (!f.isDirectory()) { // something is screwy throw new DeploymentException ("Deploy file incorrectly reported as a directory: " + di.url); } addDeployableFiles(di, f); } else { try { // Obtain a jar url for the nested jar URL nestedURL = JarUtils.extractNestedJar(di.localUrl, this.tempDeployDir); JarFile jarFile = new JarFile(nestedURL.getFile()); addDeployableJar(di, jarFile); } catch (Exception e) { log.warn("Failed to add deployable jar: " + di.localUrl, e); // // jason: should probably throw new DeploymentException // ("Failed to add deployable jar: " + jarURLString, e); // rather than make assumptions to what type of deployable // file this was that failed... // return; } } } /** * This method returns true if the name is a recognized archive file. * * It will query the MainDeployer that keeps a dynamically updated * list of known archive extensions. * * @param name The "short-name" of the URL. It will have any trailing '/' * characters removed, and any directory structure has been removed. * @param url The full url. * * @return true iff the name ends in a known archive extension: .jar, .sar, * .ear, .rar, .zip, .wsr, .war, or if the name matches the native * library conventions. */ protected boolean isDeployable(String name, URL url) { // any file under META-INF is not deployable; this method is called // also for zipped content, e.g. dir1/dir2.sar/META-INF/bla.xml if (url.getPath().indexOf("META-INF") != -1) { return false; } String[] acceptedSuffixes = mainDeployer.getSuffixOrder(); for (int i = 0; i < acceptedSuffixes.length; i++) { if (name.endsWith(acceptedSuffixes[i])) { return true; } } // this is probably obsolete return (name.endsWith(nativeSuffix) && name.startsWith(nativePrefix)); } /** * This method recursively searches the directory structure for any files * that are deployable (@see isDeployable). If a directory is found to * be deployable, then its subfiles and subdirectories are not searched. * * @param di the DeploymentInfo * @param dir The root directory to start searching. */ protected void addDeployableFiles(DeploymentInfo di, File dir) throws DeploymentException { File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; String name = file.getName(); try { URL url = file.toURL(); if (isDeployable(name, url)) { deployUrl(di, url, name); // we don't want deployable units processed any further continue; } } catch (MalformedURLException e) { log.warn("File name invalid; ignoring: " + file, e); } if (file.isDirectory()) { addDeployableFiles(di, file); } } } /** * This method searches the entire jar file for any deployable files * (@see isDeployable). * * @param di the DeploymentInfo * @param jarFile the jar file to process. */ protected void addDeployableJar(DeploymentInfo di, JarFile jarFile) throws DeploymentException { String urlPrefix = "jar:"+di.localUrl.toString()+"!/"; for (Enumeration e = jarFile.entries(); e.hasMoreElements();) { JarEntry entry = (JarEntry)e.nextElement(); String name = entry.getName(); try { URL url = new URL(urlPrefix+name); if (isDeployable(name, url)) { // Obtain a jar url for the nested jar URL nestedURL = JarUtils.extractNestedJar(url, this.tempDeployDir); deployUrl(di, nestedURL, name); } } catch (MalformedURLException mue) { // // jason: why are we eating this exception? // log.warn("Jar entry invalid; ignoring: " + name, mue); } catch (IOException ex) { log.warn("Failed to extract nested jar; ignoring: " + name, ex); } } } protected void deployUrl(DeploymentInfo di, URL url, String name) throws DeploymentException { log.debug("nested deployment: " + url); try { // // jason: need better handling for os/arch specific libraries // should be able to have multipule native libs in an archive // one for each supported platform (os/arch), we only want to // load the one for the current platform. // // This probably means explitly listing the libraries in a // deployment descriptor, which could probably also be used // to explicitly map the files, as it might be possible to // share a native lib between more than one version, no need // to duplicate the file, metadata can be used to tell us // what needs to be done. // // Also need this mapping to get around the different values // which are used by vm vendors for os.arch and such... // if (name.endsWith(nativeSuffix) && name.startsWith(nativePrefix)) { File destFile = new File(tempNativeDir, name); log.info("Loading native library: " + destFile.toString()); File parent = destFile.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } InputStream in = url.openStream(); OutputStream out = new FileOutputStream(destFile); Streams.copyb(in, out); out.flush(); out.close(); in.close(); if (loadNative) System.load(destFile.toString()); } else { new DeploymentInfo(url, di, getServer()); } } catch (Exception ex) { throw new DeploymentException ("Could not deploy sub deployment "+name+" of deployment "+di.url, ex); } } ///////////////////////////////////////////////////////////////////////// // Class Property Configuration // ///////////////////////////////////////////////////////////////////////// /** * Static configuration properties for this class. Allows easy access * to change defaults with system properties. */ protected static class ClassConfiguration extends org.jboss.util.property.PropertyContainer { private String nativeLibToken = "XxX"; public ClassConfiguration() { // properties will be settable under our enclosing classes group super(SubDeployerSupport.class); // bind the properties & the access methods bindMethod("nativeLibToken"); } public void setNativeLibToken(final String token) { this.nativeLibToken = token; } public String getNativeLibToken() { return nativeLibToken; } } /** The singleton class configuration object for this class. */ protected static final ClassConfiguration CONFIGURATION = new ClassConfiguration(); // // jason: the following needs to be done after setting up the // class config reference, so it is moved it down here. // /** * Determine the native library suffix and prefix. */ static { // get the token to use from config, incase the default needs // to be changed to resolve problem with a specific platform String token = CONFIGURATION.getNativeLibToken(); // then determine what the prefix and suffixes are for this platform String nativex = System.mapLibraryName(token); int xPos = nativex.indexOf(token); nativePrefix = nativex.substring(0, xPos); nativeSuffix = nativex.substring(xPos + 3); } }