/*
* 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.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.jboss.bootstrap.spi.ServerConfig;
import org.jboss.mx.loading.LoaderRepositoryFactory;
import org.jboss.mx.loading.LoaderRepositoryFactory.LoaderRepositoryConfig;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.net.protocol.URLLister;
import org.jboss.net.protocol.URLListerFactory;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.system.server.ServerConfigLocator;
import org.jboss.util.StringPropertyReplacer;
import org.jboss.util.Strings;
import org.jboss.util.stream.Streams;
import org.jboss.util.xml.JBossEntityResolver;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
/**
* This is the main Service Deployer API.
*
* @see org.jboss.system.Service
*
* @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
* @author <a href="mailto:David.Maplesden@orion.co.nz">David Maplesden</a>
* @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>
* @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis<a/>
* @version $Revision: 81033 $
*/
public class SARDeployer extends SubDeployerSupport
implements SARDeployerMBean
{
/** The suffixes we accept, along with their relative order */
private static final String[] DEFAULT_ENHANCED_SUFFIXES = new String[] {
"050:.deployer",
"050:-deployer.xml",
"150:.sar",
"150:-service.xml"
};
/** The deployment descriptor to look for */
private static final String JBOSS_SERVICE = "META-INF/jboss-service.xml";
/** A proxy to the ServiceController. */
private ServiceControllerMBean serviceController;
/** The server data directory. */
private File dataDir;
/** The server configuration base URL. For example,
file:/<jboss_dist_root>/server/default. Relative service
descriptor codebase elements are relative to this URL.
*/
private URL serverHomeURL;
/** A HashMap<ObjectName, DeploymentInfo> for the deployed services */
private HashMap serviceDeploymentMap = new HashMap();
/**
* A Map<String, List<String>> of the suffix to accepted archive META-INF descriptor name
* @todo externalize this
*/
private Map suffixToDescriptorMap = new ConcurrentReaderHashMap();
/** A flag indicating if the parser used for the service descriptor should be configured for namespaces */
private boolean useNamespaceAwareParser;
/**
* Default CTOR
*/
public SARDeployer()
{
setEnhancedSuffixes(DEFAULT_ENHANCED_SUFFIXES);
// Add the .har to META-INF/{jboss-service.xml,hibernate-service.xml} mapping
ArrayList tmp = new ArrayList();
tmp.add(JBOSS_SERVICE);
tmp.add("META-INF/hibernate-service.xml");
suffixToDescriptorMap.put(".har", tmp);
}
public boolean isUseNamespaceAwareParser()
{
return useNamespaceAwareParser;
}
public void setUseNamespaceAwareParser(boolean useNamespaceAwareParser)
{
this.useNamespaceAwareParser = useNamespaceAwareParser;
}
/**
* Get the associated service DeploymentInfo if found, null otherwise
*
* @param serviceName a service object name
* @return The associated service DeploymentInfo if found, null otherwise
* @jmx.managed-operation
*/
public DeploymentInfo getService(ObjectName serviceName)
{
DeploymentInfo di = null;
synchronized( serviceDeploymentMap )
{
di = (DeploymentInfo) serviceDeploymentMap.get(serviceName);
}
return di;
}
/**
* Describe <code>init</code> method here.
*
* @param di a <code>DeploymentInfo</code> value
* @exception DeploymentException if an error occurs
* @jmx.managed-operation
*/
public void init(DeploymentInfo di)
throws DeploymentException
{
try
{
if (di.url.getPath().endsWith("/"))
{
// the URL is a unpacked collection, watch the deployment descriptor
di.watch = new URL(di.url, JBOSS_SERVICE);
}
else
{
// just watch the original URL
di.watch = di.url;
}
// Get the document if not already present
parseDocument(di);
// Check for a custom loader-repository for scoping
NodeList loaders = di.document.getElementsByTagName("loader-repository");
if( loaders.getLength() > 0 )
{
Element loader = (Element) loaders.item(0);
LoaderRepositoryConfig config = LoaderRepositoryFactory.parseRepositoryConfig(loader);
di.setRepositoryInfo(config);
}
// In case there is a dependent classpath defined parse it
parseXMLClasspath(di);
// Copy local directory if local-directory element is present
NodeList lds = di.document.getElementsByTagName("local-directory");
log.debug("about to copy " + lds.getLength() + " local directories");
for (int i = 0; i< lds.getLength(); i++)
{
Element ld = (Element)lds.item(i);
String path = ld.getAttribute("path");
log.debug("about to copy local directory at " + path);
// Get the url of the local copy from the classloader.
log.debug("copying from " + di.localUrl + path + " -> " + dataDir);
inflateJar(di.localUrl, dataDir, path);
}
}
catch (DeploymentException de)
{
throw de;
}
catch (Exception e)
{
throw new DeploymentException(e);
}
// invoke super-class initialization
super.init(di);
}
/**
* Describe <code>create</code> method here.
*
* @param di a <code>DeploymentInfo</code> value
* @exception DeploymentException if an error occurs
* @jmx.managed-operation
*/
public void create(DeploymentInfo di)
throws DeploymentException
{
try
{
// install the MBeans in this descriptor
log.debug("Deploying SAR, create step: url " + di.url);
// Register the SAR UCL as an mbean so we can use it as the service loader
ObjectName uclName = di.ucl.getObjectName();
if( getServer().isRegistered(uclName) == false )
{
log.debug("Registering service UCL="+uclName);
getServer().registerMBean(di.ucl, uclName);
}
List mbeans = di.mbeans;
mbeans.clear();
List descriptorMbeans = serviceController.install(di.document.getDocumentElement(), uclName);
mbeans.addAll(descriptorMbeans);
// create the services
for (Iterator iter = di.mbeans.iterator(); iter.hasNext(); )
{
ObjectName service = (ObjectName)iter.next();
// The service won't be created until explicitly dependent mbeans are created
serviceController.create(service);
synchronized( this.serviceDeploymentMap )
{
serviceDeploymentMap.put(service, di);
}
}
// Generate a JMX notification for the create stage
super.create(di);
}
catch(DeploymentException e)
{
log.debug("create operation failed for package "+ di.url, e);
destroy(di);
throw e;
}
catch (Exception e)
{
log.debug("create operation failed for package "+ di.url, e);
destroy(di);
throw new DeploymentException("create operation failed for package "
+ di.url, e);
}
}
/**
* The <code>start</code> method starts all the mbeans in this DeploymentInfo..
*
* @param di a <code>DeploymentInfo</code> value
* @exception DeploymentException if an error occurs
* @jmx.managed-operation
*/
public void start(DeploymentInfo di) throws DeploymentException
{
log.debug("Deploying SAR, start step: url " + di.url);
try
{
// start the services
for (Iterator iter = di.mbeans.iterator(); iter.hasNext(); )
{
ObjectName service = (ObjectName)iter.next();
// The service won't be started until explicitely dependent mbeans are started
serviceController.start(service);
}
// Generate a JMX notification for the start stage
super.start(di);
}
catch (Exception e)
{
stop(di);
destroy(di);
throw new DeploymentException("start operation failed on package "
+ di.url, e);
}
}
/** The stop method invokes stop on the mbeans associatedw ith the deployment
* in reverse order relative to create.
*
* @param di the <code>DeploymentInfo</code> value to stop.
* @jmx.managed-operation
*/
public void stop(DeploymentInfo di)
{
log.debug("undeploying document " + di.url);
List services = di.mbeans;
int lastService = services.size();
// stop services in reverse order.
for (ListIterator i = services.listIterator(lastService); i.hasPrevious();)
{
ObjectName name = (ObjectName)i.previous();
log.debug("stopping mbean " + name);
try
{
serviceController.stop(name);
}
catch (Exception e)
{
log.error("Could not stop mbean: " + name, e);
} // end of try-catch
}
// Generate a JMX notification for the stop stage
try
{
super.stop(di);
}
catch(Exception ignore)
{
}
}
/** The destroy method invokes destroy on the mbeans associated with
* the deployment in reverse order relative to create.
*
* @param di a <code>DeploymentInfo</code> value
* @jmx.managed-operation
*/
public void destroy(DeploymentInfo di)
{
List services = di.mbeans;
int lastService = services.size();
for (ListIterator i = services.listIterator(lastService); i.hasPrevious();)
{
ObjectName name = (ObjectName)i.previous();
log.debug("destroying mbean " + name);
synchronized( serviceDeploymentMap )
{
serviceDeploymentMap.remove(name);
}
try
{
serviceController.destroy(name);
}
catch (Exception e)
{
log.error("Could not destroy mbean: " + name, e);
} // end of try-catch
}
for (ListIterator i = services.listIterator(lastService); i.hasPrevious();)
{
ObjectName name = (ObjectName)i.previous();
log.debug("removing mbean " + name);
try
{
serviceController.remove(name);
}
catch (Exception e)
{
log.error("Could not remove mbean: " + name, e);
} // end of try-catch
}
// Unregister the SAR UCL
try
{
ObjectName uclName = di.ucl.getObjectName();
if( getServer().isRegistered(uclName) == true )
{
log.debug("Unregistering service UCL="+uclName);
getServer().unregisterMBean(uclName);
}
}
catch(Exception ignore)
{
}
// Generate a JMX notification for the destroy stage
try
{
super.destroy(di);
}
catch(Exception ignore)
{
}
}
// ServiceMBeanSupport overrides --------------------------------
/**
* The startService method gets the mbeanProxies for MainDeployer
* and ServiceController, used elsewhere.
*
* @exception Exception if an error occurs
*/
protected void startService() throws Exception
{
super.startService();
// get the controller proxy
serviceController = (ServiceControllerMBean)
MBeanProxyExt.create(ServiceControllerMBean.class,
ServiceControllerMBean.OBJECT_NAME, server);
// Get the data directory, install url & library url
ServerConfig config = ServerConfigLocator.locate();
dataDir = config.getServerDataDir();
serverHomeURL = config.getServerHomeURL();
}
/**
* This method stops all the applications in this server.
*/
protected void stopService() throws Exception
{
// deregister with MainDeployer
super.stopService();
// Help GC
serviceController = null;
serverHomeURL = null;
dataDir = null;
}
protected ObjectName getObjectName(MBeanServer server, ObjectName name)
throws MalformedObjectNameException
{
return name == null ? OBJECT_NAME : name;
}
// Protected -----------------------------------------------------
protected File[] listFiles(final String urlspec) throws Exception
{
URL url = Strings.toURL(urlspec);
// url is already canonical thanks to Strings.toURL
File dir = new File(url.getFile());
File[] files = dir.listFiles(new java.io.FileFilter()
{
public boolean accept(File pathname)
{
String name = pathname.getName().toLowerCase();
return (name.endsWith(".jar") || name.endsWith(".zip"));
}
});
return files;
}
/**
* @param di
* @throws Exception
*/
protected void parseXMLClasspath(DeploymentInfo di)
throws Exception
{
ArrayList classpath = new ArrayList();
URLListerFactory listerFactory = new URLListerFactory();
NodeList children = di.document.getDocumentElement().getChildNodes();
for (int i = 0; i < children.getLength(); i++)
{
if (children.item(i).getNodeType() == Node.ELEMENT_NODE)
{
Element classpathElement = (Element)children.item(i);
if (classpathElement.getTagName().equals("classpath"))
{
log.debug("Found classpath element: " + classpathElement);
if (!classpathElement.hasAttribute("codebase"))
{
throw new DeploymentException
("Invalid classpath element missing codebase: " + classpathElement);
}
String codebase = classpathElement.getAttribute("codebase").trim();
// Replace any system property references like ${x}
codebase = StringPropertyReplacer.replaceProperties(codebase);
String archives = null;
if (classpathElement.hasAttribute("archives"))
{
archives = classpathElement.getAttribute("archives").trim();
// Replace any system property references like ${x}
archives = StringPropertyReplacer.replaceProperties(archives);
if ("".equals(archives))
{
archives = null;
}
}
// Convert codebase to a URL
// "." is resolved relative to the deployment
// other URLs are resolved relative to SERVER_HOME
URL codebaseUrl;
if (".".equals(codebase))
{
codebaseUrl = new URL(di.url, "./");
}
else
{
if (archives != null && codebase.endsWith("/") == false)
{
codebase += "/";
}
codebaseUrl = new URL(serverHomeURL, codebase);
}
log.debug("codebase URL is " + codebaseUrl);
if (archives == null)
{
// archives not supplied so add the codebase itself
classpath.add(codebaseUrl);
log.debug("added codebase to classpath");
}
else
{
// obtain a URLLister for the codebase and use it to obtain
// the list of URLs to add
log.debug("listing codebase for archives matching " + archives);
URLLister lister = listerFactory.createURLLister(codebaseUrl);
log.debug("URLLister class is " + lister.getClass().getName());
classpath.addAll(lister.listMembers(codebaseUrl, archives));
}
} // end of if ()
} // end of if ()
} //end of for
// Ok, now we've found the list of urls we need... deploy their classes.
Iterator jars = classpath.iterator();
while (jars.hasNext())
{
URL neededURL = (URL) jars.next();
di.addLibraryJar(neededURL);
log.debug("deployed classes for " + neededURL);
}
}
/** Parse the META-INF/jboss-service.xml descriptor
*/
protected void parseDocument(DeploymentInfo di)
throws Exception
{
InputStream stream = null;
try
{
if (di.document == null)
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(useNamespaceAwareParser);
DocumentBuilder parser = factory.newDocumentBuilder();
URL docURL = di.localUrl;
URLClassLoader localCL = di.localCl;
// Load jboss-service.xml from the jar or directory
if (di.isXML == false)
{
// Check the suffix to descriptor mapping
String[] descriptors = getDescriptorName(di);
for(int n = 0; n < descriptors.length; n ++)
{
String descriptor = descriptors[n];
docURL = localCL.findResource(descriptor);
if( docURL != null )
{
// If this is a unpacked deployment, update the watch url
if (di.url.getPath().endsWith("/"))
{
di.watch = new URL(di.url, descriptor);
log.debug("Updated watch URL to: "+di.watch);
}
break;
}
}
// No descriptors, use the default META-INF/jboss-service.xml
if( docURL == null )
docURL = localCL.findResource(JBOSS_SERVICE);
}
// Validate that the descriptor was found
if (docURL == null)
throw new DeploymentException("Failed to find META-INF/jboss-service.xml for archive " + di.shortName);
stream = docURL.openStream();
InputSource is = new InputSource(stream);
is.setSystemId(docURL.toString());
parser.setEntityResolver(new JBossEntityResolver());
di.document = parser.parse(is);
}
else
{
log.debug("Using existing deployment.document");
}
}
finally
{
// Close the stream to get around "Too many open files"-errors
try
{
stream.close();
}
catch (Exception ignore)
{
}
}
}
/**
* The <code>inflateJar</code> copies the jar entries
* from the jar url jarUrl to the directory destDir.
* It can be used on the whole jar, a directory, or
* a specific file in the jar.
*
* @param url the <code>URL</code> if the directory or entry to copy.
* @param destDir the <code>File</code> value of the directory in which to
* place the inflated copies.
*
* @exception DeploymentException if an error occurs
* @exception IOException if an error occurs
*/
protected void inflateJar(URL url, File destDir, String path)
throws DeploymentException, IOException
{
String filename = url.getFile();
JarFile jarFile = new JarFile(filename);
try
{
for (Enumeration e = jarFile.entries(); e.hasMoreElements(); )
{
JarEntry entry = (JarEntry)e.nextElement();
String name = entry.getName();
if (path == null || name.startsWith(path))
{
File outFile = new File(destDir, name);
if (!outFile.exists())
{
if (entry.isDirectory())
{
outFile.mkdirs();
}
else
{
Streams.copyb(jarFile.getInputStream(entry),
new FileOutputStream(outFile));
}
} // end of if (outFile.exists())
} // end of if (matches path)
}
}
finally
{
jarFile.close();
}
}
// Private -------------------------------------------------------
/**
* Parse the deployment url for its suffix and return a list of the accepted service
* descriptor names to look for.
*
* @param sdi - the sar deployment info
* @return the array of sar archive/directory relative names of the service descriptor. If
* there is no suffix to descriptor mapping, the default of {JBOSS_SERVICE} will be
* returned.
*/
private String[] getDescriptorName(DeploymentInfo sdi)
{
String[] descriptorNames = {JBOSS_SERVICE};
String shortName = sdi.shortName;
int dot = shortName.lastIndexOf('.');
if( dot >= 0 )
{
String suffix = shortName.substring(dot);
List descriptors = (List) suffixToDescriptorMap.get(suffix);
if( descriptors != null )
{
descriptorNames = new String[descriptors.size()];
descriptors.toArray(descriptorNames);
}
}
return descriptorNames;
}
}