/*
* 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.varia.deployment;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.jar.JarEntry;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.jboss.bootstrap.spi.ServerConfig;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.MainDeployerMBean;
import org.jboss.deployment.SubDeployer;
import org.jboss.deployment.SubDeployerSupport;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.system.server.ServerConfigLocator;
import org.jboss.util.Counter;
import org.jboss.util.file.Files;
import org.jboss.util.file.JarUtils;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.varia.deployment.convertor.Convertor;
/**
* This is the deployer for other vendor's applications
* with dynamic migration of vendor-specific DDs to
* JBoss specific DDs.
*
* @see org.jboss.varia.deployment.convertor.Convertor
*
* @author <a href="mailto:andreas@jboss.org">Andreas Schaefer</a>
* @version $Revision: 81038 $
*
* @jmx.mbean
* name="jboss.system:service=ServiceDeployer"
* extends="org.jboss.deployment.SubDeployerMBean"
*/
public class FoeDeployer
extends SubDeployerSupport
implements SubDeployer, FoeDeployerMBean
{
// Attributes ----------------------------------------------------
/** A proxy to the ServiceControllerDeployer. */
private ServiceControllerMBean serviceController;
/** The deployers scratch directory. */
private File scratchDirectory;
/** Contains the list of available converters */
private List converterList = new ArrayList();
/** an increment for tmp files */
private final Counter id = Counter.makeSynchronized(new Counter(0));
/** map of exploaded deployment destionation in scratch directoy by DeploymentInfo */
private ThreadLocal destinationByDI = new ThreadLocal() {
protected Object initialValue()
{
return new HashMap();
}
};
// SubDeployerSupport overrides ----------------------------------
/**
* Returns true if the there is a converter available to convert
* the deployment unit.
*
* @jmx.managed-operation
*/
public boolean accepts(DeploymentInfo di)
{
// delegate accepts to convertors
Iterator i = converterList.iterator();
while(i.hasNext())
{
Convertor converter = (Convertor)i.next();
if(converter.accepts(di.url))
{
return true;
}
}
return false;
}
/**
* Returns true if the there is a converter available to convert
* the deployment unit.
*/
public boolean accepts(URL url)
{
// delegate accepts to convertors
Iterator i = converterList.iterator();
while(i.hasNext())
{
Convertor converter = (Convertor)i.next();
if(converter.accepts(url))
{
return true;
}
}
return false;
}
/**
* At the init phase the deployment unit and its subdeployment units are unpacked.
* @jmx.managed-operation
*/
public void init(DeploymentInfo di)
throws DeploymentException
{
// Determine the destination for unpacking and save it in the ThreadLocal
Map destinations = (Map)destinationByDI.get();
File destination = (File)destinations.get(di.parent);
if(destination == null)
{
// Loop until for new destination
while(destination == null || destination.exists())
destination = new File(scratchDirectory, id.increment() + "." + di.shortName);
}
else
{
destination = new File(destination, di.shortName);
}
destinations.put(di, destination);
destinationByDI.set(destinations);
try
{
log.debug("unpacking to " + destination);
inflateJar(di.localUrl, destination);
}
catch(Exception e)
{
throw new DeploymentException("Unpacking failed: ", e);
}
// invoke super class' initialization
super.init(di);
}
/**
* At the create phase, the conversion and packing is done.
* @jmx.managed-operation
*/
public void create(DeploymentInfo di)
throws DeploymentException
{
try
{
// fetch the destionation of unpacked deployment from ThreadLocal
Map destinations = (Map)destinationByDI.get();
File inflateDest = (File)destinations.get(di);
// Look for the converter that accepts vendor specific deployment descriptors
// and let it convert them
Iterator i = converterList.iterator();
while(i.hasNext())
{
Convertor converter = (Convertor)i.next();
if(converter.accepts(di.url))
{
// Convert them to JBoss specific DDs
converter.convert(di, inflateDest);
// Now conversion is done and we can leave
break;
}
}
// deflate
File deflateDest = (File)destinations.get(di.parent);
if(deflateDest == null)
deflateDest = scratchDirectory;
String validName = null;
if(di.shortName.endsWith(".wl"))
validName = di.shortName.substring(0, di.shortName.length()-3);
else
validName = di.shortName.substring( 0, di.shortName.length() - 4 ) + "jar";
File convertedUnit = new File(deflateDest, validName);
log.debug("deflating to " + convertedUnit);
deflateJar(convertedUnit, inflateDest);
// remove unpacked deployment unit
Files.delete(inflateDest);
// copy the converted app back to the deployment directory
if(di.parent == null)
copyFile(convertedUnit, new File(di.url.getFile()).getParentFile());
}
catch(Exception e)
{
log.error("Conversion error: ", e);
}
}
/**
* This method stops this deployment because it is not of any
* use anymore (conversion is done)
* @jmx.managed-operation
*/
public void start(DeploymentInfo di)
throws DeploymentException
{
stop(di);
destroy(di);
}
/**
* @jmx.managed-operation
*/
public void stop(DeploymentInfo di)
{
log.debug("undeploying application: " + di.url);
}
/**
* @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 );
try
{
serviceController.destroy(name);
}
catch(Exception e)
{
log.error("Could not destroy mbean: " + name, e);
}
}
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);
}
}
}
/**
* This method is called in SubDeployerSupport.processNestedDeployments()
* The method is overriden to deploy the deployments acceptable by FoeDeployer only.
*/
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
// Append the ".wl" suffix to prevent other than FoeDeployer deployers'
// attempts to deploy the deployment unit
URL nestedURL = JarUtils.extractNestedJar(url, this.tempDeployDir);
File file = new File(nestedURL.getFile());
File wlFile = new File(nestedURL.getFile() + ".wl");
file.renameTo(wlFile);
if(accepts(wlFile.toURL()))
{
deployUrl(di, wlFile.toURL(), name + ".wl");
}
else
{
// if the deployment isn't accepted rename it back
wlFile.renameTo(new File(nestedURL.getFile()));
}
}
}
catch(MalformedURLException mue)
{
log.warn("Jar entry invalid; ignoring: " + name, mue);
}
catch(IOException ex)
{
log.warn("Failed to extract nested jar; ignoring: " + name, ex);
}
}
}
/**
* The startService method
* - gets the mbeanProxies for MainDeployer and ServiceController;
* - creates scratch directory for foe work.
*
* @exception Exception if an error occurs
*/
protected void startService()
throws Exception
{
mainDeployer = (MainDeployerMBean) MBeanProxyExt.create(
MainDeployerMBean.class,
MainDeployerMBean.OBJECT_NAME,
server
);
// get the controller proxy
serviceController = (ServiceControllerMBean) MBeanProxyExt.create(
ServiceControllerMBean.class,
ServiceControllerMBean.OBJECT_NAME,
server
);
ServerConfig config = ServerConfigLocator.locate();
// build the scratch directory
File tempDirectory = config.getServerTempDir();
scratchDirectory = new File(tempDirectory, "foe");
if(!scratchDirectory.exists())
scratchDirectory.mkdirs();
// Note: this should go the last.
// scratch directory must be created before this call
super.startService();
}
/**
* Returns the ObjectName
*/
protected ObjectName getObjectName(MBeanServer server, ObjectName name)
throws MalformedObjectNameException
{
return name == null ? OBJECT_NAME : name;
}
// FoeDeployerMBean implementation -------------------------------
/**
* Add a new conveter to the list. If the same converter is
* added, this new one won't be added, meaning everything stays the same.
* This method is normally called by a Converter to be
* called by this deployer to convert.
*
* @param converter New Converter to be added
*
* @jmx.managed-operation
*/
public void addConvertor(Convertor converter)
{
converterList.add(converter);
// try to deploy waiting deployment units
// note: there is no need to synchronize, because MainDeployer
// returns a copy of waiting deployments
Collection waitingDeployments = mainDeployer.listWaitingForDeployer();
if((waitingDeployments != null) && (waitingDeployments.size() > 0))
{
for( Iterator iter = waitingDeployments.iterator(); iter.hasNext(); )
{
DeploymentInfo di = (DeploymentInfo)iter.next();
// check whether the converter accepts the deployment
if(!converter.accepts(di.url))
continue;
log.debug("trying to deploy with new converter: " + di.shortName);
try
{
mainDeployer.redeploy(di);
}
catch (DeploymentException e)
{
log.error("DeploymentException while trying to deploy a package with new converter", e);
}
}
}
}
/**
* Removes a conveter from the list of converters. If the
* converter does not exist nothing happens.
* This method is normally called by a Converter to be removed
* from the list if not serving anymore.
*
* @param converter Conveter to be removed from the list
*
* @jmx.managed-operation
*/
public void removeConvertor(Convertor converter)
{
converterList.remove(converter);
}
// Private --------------------------------------------------------
/**
* The <code>inflateJar</code> copies the jar entries
* from the jar url jarUrl to the directory destDir.
*
* @param fileURL URL pointing to the file to be inflated
* @param destinationDirectory Directory to which the content shall be inflated to
*
* @exception DeploymentException if an error occurs
* @exception IOException if an error occurs
*/
protected void inflateJar( URL fileURL, File destinationDirectory )
throws DeploymentException, IOException
{
File destFile = new File(fileURL.getFile());
InputStream input = new FileInputStream(fileURL.getFile());
JarUtils.unjar(input, destinationDirectory);
// input is closed in unjar();
}
/**
* Deflate a given directory into a JAR file
*
* @param jarFile The JAR file to be created
* @param root Root directory of the files to be included (this directory
* will not be included in the path of the JAR content)
**/
private void deflateJar( File jarFile, File root )
throws Exception
{
OutputStream output = new FileOutputStream(jarFile);
JarUtils.jar(output, root.listFiles(), null, null, null);
output.close();
}
/**
* Copies the given File to a new destination with the same name
*
* @param source The source file to be copied
* @param destinationDirectory File pointing to the destination directory
**/
private void copyFile(File source, File destinationDirectory)
throws Exception
{
File target = new File( destinationDirectory, source.getName() );
// Move may fail if target is used (because it is deployed)
// Use Files.copy instead
Files.copy(source, target);
}
}