/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, 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.ejb;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import javax.management.ObjectName;
import javax.security.jacc.PolicyContext;
import javax.transaction.TransactionManager;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.J2eeApplicationMetaData;
import org.jboss.deployment.SubDeployerExt;
import org.jboss.deployment.SubDeployerSupport;
import org.jboss.ejb.plugins.EnterpriseBeanPolicyContextHandler;
import org.jboss.logging.Logger;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.XmlFileLoader;
import org.jboss.mx.loading.LoaderRepositoryFactory;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.mx.util.ObjectNameConverter;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.verifier.BeanVerifier;
import org.jboss.verifier.event.VerificationEvent;
import org.jboss.verifier.event.VerificationListener;
import org.w3c.dom.Element;
/**
* A EJBDeployer is used to deploy EJB applications. It can be given a
* URL to an EJB-jar or EJB-JAR XML file, which will be used to instantiate
* containers and make them available for invocation.
*
* @jmx:mbean
* name="jboss.ejb:service=EJBDeployer"
* extends="org.jboss.deployment.SubDeployerMBean"
*
* @see Container
*
* @version $Revision: 61543 $
* @author Rickard Öberg
* @author Marc Fleury
* @author Juha Lindfors
* @author Sebastien Alborini
* @author Peter Antman
* @author Scott Stark
* @author Sacha Labourey
* @author Jason Dillon
* @author Christoph G. Jung
* @author Thomas Diesler
* @author Dimitris Andreadis
*/
public class EJBDeployer extends SubDeployerSupport
implements EJBDeployerMBean
{
/** The suffixes we accept, along with their relative order */
private static final String[] DEFAULT_ENHANCED_SUFFIXES = new String[] {
"400:.jar"
};
private ServiceControllerMBean serviceController;
/** A map of current deployments. */
private HashMap deployments = new HashMap();
/** Verify EJB-jar contents on deployments */
private boolean verifyDeployments;
/** Enable verbose verification. */
private boolean verifierVerbose;
/** Enable strict verification: deploy JAR only if Verifier reports
* no problems */
private boolean strictVerifier;
/** Enable metrics interceptor */
private boolean metricsEnabled;
/** A flag indicating if deployment descriptors should be validated */
private boolean validateDTDs;
/** Service name for the web service */
private ObjectName webServiceName;
private ObjectName transactionManagerServiceName;
private TransactionManager tm;
private boolean callByValue;
/** Hold a proxy reference to myself, used when registering to MainDeployer */
private SubDeployerExt thisProxy;
/**
* Default CTOR
*/
public EJBDeployer()
{
setEnhancedSuffixes(DEFAULT_ENHANCED_SUFFIXES);
}
/**
* @jmx:managed-attribute
*
* @return whether ear deployments should be call by value
*/
public boolean isCallByValue()
{
return callByValue;
}
/**
* @jmx:managed-attribute
*
* @param callByValue whether ear deployments should be call by value
*/
public void setCallByValue(boolean callByValue)
{
this.callByValue = callByValue;
}
/**
* Returns the deployed applications.
*
* @jmx:managed-operation
*/
public Iterator listDeployedApplications()
{
return deployments.values().iterator();
}
/**
* Get a reference to the ServiceController
*/
protected void startService() throws Exception
{
serviceController = (ServiceControllerMBean)
MBeanProxyExt.create(ServiceControllerMBean.class,
ServiceControllerMBean.OBJECT_NAME, server);
tm = (TransactionManager)getServer().getAttribute(transactionManagerServiceName,
"TransactionManager");
// Register the JAAC EJB PolicyContextHandlers
// Each context handler can only be registered once per vm
Set keys = PolicyContext.getHandlerKeys();
if (!keys.contains(EnterpriseBeanPolicyContextHandler.EJB_CONTEXT_KEY))
{
EnterpriseBeanPolicyContextHandler beanHandler = new EnterpriseBeanPolicyContextHandler();
PolicyContext.registerHandler(EnterpriseBeanPolicyContextHandler.EJB_CONTEXT_KEY,
beanHandler, false);
}
if (!keys.contains(BeanMetaDataPolicyContextHandler.METADATA_CONTEXT_KEY))
{
BeanMetaDataPolicyContextHandler metadataHandler = new BeanMetaDataPolicyContextHandler();
PolicyContext.registerHandler(BeanMetaDataPolicyContextHandler.METADATA_CONTEXT_KEY,
metadataHandler, false);
}
if (!keys.contains(EJBArgsPolicyContextHandler.EJB_ARGS_KEY))
{
EJBArgsPolicyContextHandler argsHandler = new EJBArgsPolicyContextHandler();
PolicyContext.registerHandler(EJBArgsPolicyContextHandler.EJB_ARGS_KEY,
argsHandler, false);
}
if (!keys.contains(SOAPMsgPolicyContextHandler.SEI_ARGS_KEY))
{
SOAPMsgPolicyContextHandler msgHandler = new SOAPMsgPolicyContextHandler();
PolicyContext.registerHandler(SOAPMsgPolicyContextHandler.SEI_ARGS_KEY,
msgHandler, false);
}
// make a proxy to myself, so that calls from the MainDeployer
// can go through the MBeanServer, so interceptors can be added
thisProxy = (SubDeployerExt)
MBeanProxyExt.create(SubDeployerExt.class, super.getServiceName(), super.getServer());
// Register with the main deployer
mainDeployer.addDeployer(thisProxy);
}
/**
* Implements the template method in superclass. This method stops all the
* applications in this server.
*/
protected void stopService() throws Exception
{
for( Iterator modules = deployments.values().iterator();
modules.hasNext(); )
{
DeploymentInfo di = (DeploymentInfo) modules.next();
stop(di);
}
// avoid concurrent modification exception
for( Iterator modules = new ArrayList(deployments.values()).iterator();
modules.hasNext(); )
{
DeploymentInfo di = (DeploymentInfo) modules.next();
destroy(di);
}
deployments.clear();
// deregister with MainDeployer
mainDeployer.removeDeployer(thisProxy);
serviceController = null;
tm = null;
}
/**
* Enables/disables the application bean verification upon deployment.
*
* @jmx:managed-attribute
*
* @param verify true to enable; false to disable
*/
public void setVerifyDeployments( boolean verify )
{
verifyDeployments = verify;
}
/**
* Returns the state of bean verifier (on/off)
*
* @jmx:managed-attribute
*
* @return true if enabled; false otherwise
*/
public boolean getVerifyDeployments()
{
return verifyDeployments;
}
/**
* Enables/disables the verbose mode on the verifier.
*
* @jmx:managed-attribute
*
* @param verbose true to enable; false to disable
*/
public void setVerifierVerbose(boolean verbose)
{
verifierVerbose = verbose;
}
/**
* Returns the state of the bean verifier (verbose/non-verbose mode)
*
* @jmx:managed-attribute
*
* @return true if enabled; false otherwise
*/
public boolean getVerifierVerbose()
{
return verifierVerbose;
}
/**
* Enables/disables the strict mode on the verifier.
*
* @jmx:managed-attribute
*
* @param strictVerifier true
to enable; false
* to disable
*/
public void setStrictVerifier( boolean strictVerifier )
{
this.strictVerifier = strictVerifier;
}
/**
* Returns the mode of the bean verifier (strict/non-strict mode)
*
* @jmx:managed-attribute
*
* @return true
if the Verifier is in strict mode,
* false
otherwise
*/
public boolean getStrictVerifier()
{
return strictVerifier;
}
/**
* Enables/disables the metrics interceptor for containers.
*
* @jmx:managed-attribute
*
* @param enable true to enable; false to disable
*/
public void setMetricsEnabled(boolean enable)
{
metricsEnabled = enable;
}
/**
* Checks if this container factory initializes the metrics interceptor.
*
* @jmx:managed-attribute
*
* @return true if metrics are enabled; false otherwise
*/
public boolean isMetricsEnabled()
{
return metricsEnabled;
}
/**
* Get the flag indicating that ejb-jar.dtd, jboss.dtd &
* jboss-web.dtd conforming documents should be validated
* against the DTD.
*
* @jmx:managed-attribute
*/
public boolean getValidateDTDs()
{
return validateDTDs;
}
/**
* Set the flag indicating that ejb-jar.dtd, jboss.dtd &
* jboss-web.dtd conforming documents should be validated
* against the DTD.
*
* @jmx:managed-attribute
*/
public void setValidateDTDs(boolean validate)
{
this.validateDTDs = validate;
}
/**
* Get the WebServiceName value.
* @return the WebServiceName value.
*
* @jmx:managed-attribute
*/
public ObjectName getWebServiceName()
{
return webServiceName;
}
/**
* Set the WebServiceName value.
* @param webServiceName The new WebServiceName value.
*
* @jmx:managed-attribute
*/
public void setWebServiceName(ObjectName webServiceName)
{
this.webServiceName = webServiceName;
}
/**
* Get the TransactionManagerServiceName value.
* @return the TransactionManagerServiceName value.
*
* @jmx:managed-attribute
*/
public ObjectName getTransactionManagerServiceName()
{
return transactionManagerServiceName;
}
/**
* Set the TransactionManagerServiceName value.
* @param transactionManagerServiceName The new TransactionManagerServiceName value.
*
* @jmx:managed-attribute
*/
public void setTransactionManagerServiceName(ObjectName transactionManagerServiceName)
{
this.transactionManagerServiceName = transactionManagerServiceName;
}
public boolean accepts(DeploymentInfo di)
{
// To be accepted the deployment's root name must end in .jar or .jar/
if (super.accepts(di) == false)
{
return false;
}
// However the jar must also contain at least one ejb-jar.xml
boolean accepts = false;
try
{
URL dd = di.localCl.findResource("META-INF/ejb-jar.xml");
if (dd == null)
{
return false;
}
String urlStr = di.url.getFile();
// If the DD url is not a subset of the urlStr then this is coming
// from a jar referenced by the deployment jar manifest and the
// this deployment jar it should not be treated as an ejb-jar
if( di.localUrl != null )
{
urlStr = di.localUrl.toString();
}
String ddStr = dd.toString();
if ( ddStr.indexOf(urlStr) >= 0 )
{
accepts = true;
}
}
catch( Exception ignore )
{
}
return accepts;
}
public void init(DeploymentInfo di)
throws DeploymentException
{
log.debug("init, "+di.shortName);
try
{
if( di.url.getProtocol().equalsIgnoreCase("file") )
{
File file = new File(di.url.getFile());
if( !file.isDirectory() )
{
// If not directory we watch the package
di.watch = di.url;
}
else
{
// If directory we watch the xml files
di.watch = new URL(di.url, "META-INF/ejb-jar.xml");
}
}
else
{
// We watch the top only, no directory support
di.watch = di.url;
}
// Check for a loader-repository
XmlFileLoader xfl = new XmlFileLoader();
InputStream in = di.localCl.getResourceAsStream("META-INF/jboss.xml");
if( in != null )
{
try
{
Element jboss = xfl.getDocument(in, "META-INF/jboss.xml").getDocumentElement();
// Check for a ejb level class loading config
Element loader = MetaData.getOptionalChild(jboss, "loader-repository");
if( loader != null )
{
LoaderRepositoryFactory.LoaderRepositoryConfig config =
LoaderRepositoryFactory.parseRepositoryConfig(loader);
di.setRepositoryInfo(config);
}
}
finally
{
in.close();
}
}
}
catch (Exception e)
{
if (e instanceof DeploymentException)
throw (DeploymentException)e;
throw new DeploymentException( "failed to initialize", e );
}
// invoke super-class initialization
super.init(di);
}
/**
* This is here as a reminder that we may not want to allow ejb jars to
* have arbitrary sub deployments. Currently we do.
* @param di
* @throws DeploymentException
*/
protected void processNestedDeployments(DeploymentInfo di)
throws DeploymentException
{
super.processNestedDeployments(di);
}
public synchronized void create(DeploymentInfo di)
throws DeploymentException
{
log.debug("create, "+di.shortName);
ApplicationMetaData ejbMetaData = null;
try
{
// Initialize the annotations classloader
URL loaderURL = (di.localUrl != null ? di.localUrl : di.url);
di.annotationsCl = new URLClassLoader(new URL[] { loaderURL }, di.ucl);
// Create a file loader with which to load the files
XmlFileLoader efm = new XmlFileLoader(validateDTDs);
efm.setClassLoader(di.localCl);
// redirect to alternative DD
URL alternativeDD = null;
if (di.alternativeDD != null)
{
File contentsDir = new File(di.url.getPath()).getParentFile();
alternativeDD = new File(contentsDir, di.alternativeDD).toURL();
}
// Load XML
di.metaData = ejbMetaData = efm.load(alternativeDD);
// inherit the security setup from jboss-app.xml
if (di.parent != null && di.parent.metaData instanceof J2eeApplicationMetaData)
{
J2eeApplicationMetaData appMetaData = (J2eeApplicationMetaData)di.parent.metaData;
if (ejbMetaData.getSecurityDomain() == null)
ejbMetaData.setSecurityDomain(appMetaData.getSecurityDomain());
if (ejbMetaData.getUnauthenticatedPrincipal() == null)
ejbMetaData.setUnauthenticatedPrincipal(appMetaData.getUnauthenticatedPrincipal());
ejbMetaData.getAssemblyDescriptor().mergeSecurityRoles(appMetaData.getSecurityRoles());
}
}
catch (Exception e)
{
if (e instanceof DeploymentException)
throw (DeploymentException)e;
throw new DeploymentException( "Failed to load metaData", e );
}
if( verifyDeployments )
{
// we have a positive attitude
boolean allOK = true;
// wrapping this into a try - catch block to prevent errors in
// verifier from stopping the deployment
try
{
BeanVerifier verifier = new BeanVerifier();
// add a listener so we can log the results
verifier.addVerificationListener(new VerificationListener()
{
Logger verifierLog = Logger.getLogger(EJBDeployer.class,
"verifier" );
public void beanChecked(VerificationEvent event)
{
verifierLog.debug( "Bean checked: " + event.getMessage() );
}
public void specViolation(VerificationEvent event)
{
verifierLog.warn( "EJB spec violation: " +
(verifierVerbose ? event.getVerbose() : event.getMessage()));
}
});
log.debug("Verifying " + di.url);
verifier.verify( di.url, (ApplicationMetaData) di.metaData,
di.ucl );
allOK = verifier.getSuccess();
}
catch (Throwable t)
{
log.warn("Verify failed; continuing", t );
allOK = false;
}
// If the verifier is in strict mode and an error/warning
// was found in the Verification process, throw a Deployment
// Exception
if( strictVerifier && !allOK )
{
throw new DeploymentException( "Verification of Enterprise " +
"Beans failed, see above for error messages." );
}
}
// Create an MBean for the EJB module
try
{
EjbModule ejbModule = new EjbModule(di, tm, webServiceName);
String name = ejbMetaData.getJmxName();
if( name == null )
{
name = EjbModule.BASE_EJB_MODULE_NAME + ",module=" + di.shortName;
}
// Build an escaped JMX name including deployment shortname
ObjectName ejbModuleName = ObjectNameConverter.convert(name);
// Check that the name is not registered
if( server.isRegistered(ejbModuleName) == true )
{
log.debug("The EJBModule name: "+ejbModuleName
+"is already registered, adding uid="+System.identityHashCode(ejbModule));
name = name + ",uid="+System.identityHashCode(ejbModule);
ejbModuleName = ObjectNameConverter.convert(name);
}
server.registerMBean(ejbModule, ejbModuleName);
di.deployedObject = ejbModuleName;
log.debug( "Deploying: " + di.url );
// Invoke the create life cycle method
serviceController.create(di.deployedObject);
}
catch (Exception e)
{
throw new DeploymentException("Error during create of EjbModule: "
+ di.url, e);
}
super.create(di);
}
public synchronized void start(DeploymentInfo di)
throws DeploymentException
{
try
{
// Start application
log.debug( "start application, deploymentInfo: " + di +
", short name: " + di.shortName +
", parent short name: " +
(di.parent == null ? "null" : di.parent.shortName) );
serviceController.start(di.deployedObject);
log.info( "Deployed: " + di.url );
// Register deployment. Use the application name in the hashtable
// FIXME: this is obsolete!! (really?!)
deployments.put(di.url, di);
}
catch (Exception e)
{
stop(di);
destroy(di);
throw new DeploymentException( "Could not deploy " + di.url, e );
}
super.start(di);
}
public void stop(DeploymentInfo di)
throws DeploymentException
{
log.info( "Undeploying: " + di.url );
try
{
if (di.deployedObject != null)
serviceController.stop(di.deployedObject);
}
catch (Exception e)
{
throw new DeploymentException( "problem stopping ejb module: " +
di.url, e );
}
super.stop(di);
}
public void destroy(DeploymentInfo di)
throws DeploymentException
{
// FIXME: If the put() is obsolete above, this is obsolete, too
deployments.remove(di.url);
try
{
if (di.deployedObject != null)
{
serviceController.destroy( di.deployedObject );
serviceController.remove( di.deployedObject );
}
}
catch (Exception e)
{
throw new DeploymentException( "problem destroying ejb module: " +
di.url, e );
}
super.destroy(di);
}
}