001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb;
023:
024: import java.io.File;
025: import java.io.InputStream;
026: import java.net.URL;
027: import java.net.URLClassLoader;
028: import java.util.ArrayList;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.Set;
032:
033: import javax.management.ObjectName;
034: import javax.security.jacc.PolicyContext;
035: import javax.transaction.TransactionManager;
036:
037: import org.jboss.deployment.DeploymentException;
038: import org.jboss.deployment.DeploymentInfo;
039: import org.jboss.deployment.J2eeApplicationMetaData;
040: import org.jboss.deployment.SubDeployerExt;
041: import org.jboss.deployment.SubDeployerSupport;
042: import org.jboss.ejb.plugins.EnterpriseBeanPolicyContextHandler;
043: import org.jboss.logging.Logger;
044: import org.jboss.metadata.ApplicationMetaData;
045: import org.jboss.metadata.MetaData;
046: import org.jboss.metadata.XmlFileLoader;
047: import org.jboss.mx.loading.LoaderRepositoryFactory;
048: import org.jboss.mx.util.MBeanProxyExt;
049: import org.jboss.mx.util.ObjectNameConverter;
050: import org.jboss.system.ServiceControllerMBean;
051: import org.jboss.verifier.BeanVerifier;
052: import org.jboss.verifier.event.VerificationEvent;
053: import org.jboss.verifier.event.VerificationListener;
054: import org.w3c.dom.Element;
055:
056: /**
057: * A EJBDeployer is used to deploy EJB applications. It can be given a
058: * URL to an EJB-jar or EJB-JAR XML file, which will be used to instantiate
059: * containers and make them available for invocation.
060: *
061: * @jmx:mbean
062: * name="jboss.ejb:service=EJBDeployer"
063: * extends="org.jboss.deployment.SubDeployerMBean"
064: *
065: * @see Container
066: *
067: * @version <tt>$Revision: 61543 $</tt>
068: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
069: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
070: * @author <a href="mailto:jplindfo@helsinki.fi">Juha Lindfors</a>
071: * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
072: * @author <a href="mailto:peter.antman@tim.se">Peter Antman</a>
073: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
074: * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>
075: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
076: * @author <a href="mailto:christoph.jung@infor.de">Christoph G. Jung</a>
077: * @author <a href="mailto:thomas.diesler@arcor.de">Thomas Diesler</a>
078: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
079: */
080: public class EJBDeployer extends SubDeployerSupport implements
081: EJBDeployerMBean {
082: /** The suffixes we accept, along with their relative order */
083: private static final String[] DEFAULT_ENHANCED_SUFFIXES = new String[] { "400:.jar" };
084:
085: private ServiceControllerMBean serviceController;
086:
087: /** A map of current deployments. */
088: private HashMap deployments = new HashMap();
089:
090: /** Verify EJB-jar contents on deployments */
091: private boolean verifyDeployments;
092:
093: /** Enable verbose verification. */
094: private boolean verifierVerbose;
095:
096: /** Enable strict verification: deploy JAR only if Verifier reports
097: * no problems */
098: private boolean strictVerifier;
099:
100: /** Enable metrics interceptor */
101: private boolean metricsEnabled;
102:
103: /** A flag indicating if deployment descriptors should be validated */
104: private boolean validateDTDs;
105:
106: /** Service name for the web service */
107: private ObjectName webServiceName;
108:
109: private ObjectName transactionManagerServiceName;
110: private TransactionManager tm;
111:
112: private boolean callByValue;
113:
114: /** Hold a proxy reference to myself, used when registering to MainDeployer */
115: private SubDeployerExt this Proxy;
116:
117: /**
118: * Default CTOR
119: */
120: public EJBDeployer() {
121: setEnhancedSuffixes(DEFAULT_ENHANCED_SUFFIXES);
122: }
123:
124: /**
125: * @jmx:managed-attribute
126: *
127: * @return whether ear deployments should be call by value
128: */
129: public boolean isCallByValue() {
130: return callByValue;
131: }
132:
133: /**
134: * @jmx:managed-attribute
135: *
136: * @param callByValue whether ear deployments should be call by value
137: */
138: public void setCallByValue(boolean callByValue) {
139: this .callByValue = callByValue;
140: }
141:
142: /**
143: * Returns the deployed applications.
144: *
145: * @jmx:managed-operation
146: */
147: public Iterator listDeployedApplications() {
148: return deployments.values().iterator();
149: }
150:
151: /**
152: * Get a reference to the ServiceController
153: */
154: protected void startService() throws Exception {
155: serviceController = (ServiceControllerMBean) MBeanProxyExt
156: .create(ServiceControllerMBean.class,
157: ServiceControllerMBean.OBJECT_NAME, server);
158: tm = (TransactionManager) getServer().getAttribute(
159: transactionManagerServiceName, "TransactionManager");
160:
161: // Register the JAAC EJB PolicyContextHandlers
162: // Each context handler can only be registered once per vm
163: Set keys = PolicyContext.getHandlerKeys();
164: if (!keys
165: .contains(EnterpriseBeanPolicyContextHandler.EJB_CONTEXT_KEY)) {
166: EnterpriseBeanPolicyContextHandler beanHandler = new EnterpriseBeanPolicyContextHandler();
167: PolicyContext.registerHandler(
168: EnterpriseBeanPolicyContextHandler.EJB_CONTEXT_KEY,
169: beanHandler, false);
170: }
171: if (!keys
172: .contains(BeanMetaDataPolicyContextHandler.METADATA_CONTEXT_KEY)) {
173: BeanMetaDataPolicyContextHandler metadataHandler = new BeanMetaDataPolicyContextHandler();
174: PolicyContext
175: .registerHandler(
176: BeanMetaDataPolicyContextHandler.METADATA_CONTEXT_KEY,
177: metadataHandler, false);
178: }
179: if (!keys.contains(EJBArgsPolicyContextHandler.EJB_ARGS_KEY)) {
180: EJBArgsPolicyContextHandler argsHandler = new EJBArgsPolicyContextHandler();
181: PolicyContext.registerHandler(
182: EJBArgsPolicyContextHandler.EJB_ARGS_KEY,
183: argsHandler, false);
184: }
185: if (!keys.contains(SOAPMsgPolicyContextHandler.SEI_ARGS_KEY)) {
186: SOAPMsgPolicyContextHandler msgHandler = new SOAPMsgPolicyContextHandler();
187: PolicyContext.registerHandler(
188: SOAPMsgPolicyContextHandler.SEI_ARGS_KEY,
189: msgHandler, false);
190: }
191:
192: // make a proxy to myself, so that calls from the MainDeployer
193: // can go through the MBeanServer, so interceptors can be added
194: this Proxy = (SubDeployerExt) MBeanProxyExt.create(
195: SubDeployerExt.class, super .getServiceName(), super
196: .getServer());
197:
198: // Register with the main deployer
199: mainDeployer.addDeployer(this Proxy);
200: }
201:
202: /**
203: * Implements the template method in superclass. This method stops all the
204: * applications in this server.
205: */
206: protected void stopService() throws Exception {
207:
208: for (Iterator modules = deployments.values().iterator(); modules
209: .hasNext();) {
210: DeploymentInfo di = (DeploymentInfo) modules.next();
211: stop(di);
212: }
213:
214: // avoid concurrent modification exception
215: for (Iterator modules = new ArrayList(deployments.values())
216: .iterator(); modules.hasNext();) {
217: DeploymentInfo di = (DeploymentInfo) modules.next();
218: destroy(di);
219: }
220: deployments.clear();
221:
222: // deregister with MainDeployer
223: mainDeployer.removeDeployer(this Proxy);
224:
225: serviceController = null;
226: tm = null;
227: }
228:
229: /**
230: * Enables/disables the application bean verification upon deployment.
231: *
232: * @jmx:managed-attribute
233: *
234: * @param verify true to enable; false to disable
235: */
236: public void setVerifyDeployments(boolean verify) {
237: verifyDeployments = verify;
238: }
239:
240: /**
241: * Returns the state of bean verifier (on/off)
242: *
243: * @jmx:managed-attribute
244: *
245: * @return true if enabled; false otherwise
246: */
247: public boolean getVerifyDeployments() {
248: return verifyDeployments;
249: }
250:
251: /**
252: * Enables/disables the verbose mode on the verifier.
253: *
254: * @jmx:managed-attribute
255: *
256: * @param verbose true to enable; false to disable
257: */
258: public void setVerifierVerbose(boolean verbose) {
259: verifierVerbose = verbose;
260: }
261:
262: /**
263: * Returns the state of the bean verifier (verbose/non-verbose mode)
264: *
265: * @jmx:managed-attribute
266: *
267: * @return true if enabled; false otherwise
268: */
269: public boolean getVerifierVerbose() {
270: return verifierVerbose;
271: }
272:
273: /**
274: * Enables/disables the strict mode on the verifier.
275: *
276: * @jmx:managed-attribute
277: *
278: * @param strictVerifier <code>true</code> to enable; <code>false</code>
279: * to disable
280: */
281: public void setStrictVerifier(boolean strictVerifier) {
282: this .strictVerifier = strictVerifier;
283: }
284:
285: /**
286: * Returns the mode of the bean verifier (strict/non-strict mode)
287: *
288: * @jmx:managed-attribute
289: *
290: * @return <code>true</code> if the Verifier is in strict mode,
291: * <code>false</code> otherwise
292: */
293: public boolean getStrictVerifier() {
294: return strictVerifier;
295: }
296:
297: /**
298: * Enables/disables the metrics interceptor for containers.
299: *
300: * @jmx:managed-attribute
301: *
302: * @param enable true to enable; false to disable
303: */
304: public void setMetricsEnabled(boolean enable) {
305: metricsEnabled = enable;
306: }
307:
308: /**
309: * Checks if this container factory initializes the metrics interceptor.
310: *
311: * @jmx:managed-attribute
312: *
313: * @return true if metrics are enabled; false otherwise
314: */
315: public boolean isMetricsEnabled() {
316: return metricsEnabled;
317: }
318:
319: /**
320: * Get the flag indicating that ejb-jar.dtd, jboss.dtd &
321: * jboss-web.dtd conforming documents should be validated
322: * against the DTD.
323: *
324: * @jmx:managed-attribute
325: */
326: public boolean getValidateDTDs() {
327: return validateDTDs;
328: }
329:
330: /**
331: * Set the flag indicating that ejb-jar.dtd, jboss.dtd &
332: * jboss-web.dtd conforming documents should be validated
333: * against the DTD.
334: *
335: * @jmx:managed-attribute
336: */
337: public void setValidateDTDs(boolean validate) {
338: this .validateDTDs = validate;
339: }
340:
341: /**
342: * Get the WebServiceName value.
343: * @return the WebServiceName value.
344: *
345: * @jmx:managed-attribute
346: */
347: public ObjectName getWebServiceName() {
348: return webServiceName;
349: }
350:
351: /**
352: * Set the WebServiceName value.
353: * @param webServiceName The new WebServiceName value.
354: *
355: * @jmx:managed-attribute
356: */
357: public void setWebServiceName(ObjectName webServiceName) {
358: this .webServiceName = webServiceName;
359: }
360:
361: /**
362: * Get the TransactionManagerServiceName value.
363: * @return the TransactionManagerServiceName value.
364: *
365: * @jmx:managed-attribute
366: */
367: public ObjectName getTransactionManagerServiceName() {
368: return transactionManagerServiceName;
369: }
370:
371: /**
372: * Set the TransactionManagerServiceName value.
373: * @param transactionManagerServiceName The new TransactionManagerServiceName value.
374: *
375: * @jmx:managed-attribute
376: */
377: public void setTransactionManagerServiceName(
378: ObjectName transactionManagerServiceName) {
379: this .transactionManagerServiceName = transactionManagerServiceName;
380: }
381:
382: public boolean accepts(DeploymentInfo di) {
383: // To be accepted the deployment's root name must end in .jar or .jar/
384: if (super .accepts(di) == false) {
385: return false;
386: }
387: // However the jar must also contain at least one ejb-jar.xml
388: boolean accepts = false;
389: try {
390: URL dd = di.localCl.findResource("META-INF/ejb-jar.xml");
391: if (dd == null) {
392: return false;
393: }
394: String urlStr = di.url.getFile();
395:
396: // If the DD url is not a subset of the urlStr then this is coming
397: // from a jar referenced by the deployment jar manifest and the
398: // this deployment jar it should not be treated as an ejb-jar
399: if (di.localUrl != null) {
400: urlStr = di.localUrl.toString();
401: }
402:
403: String ddStr = dd.toString();
404: if (ddStr.indexOf(urlStr) >= 0) {
405: accepts = true;
406: }
407: } catch (Exception ignore) {
408: }
409:
410: return accepts;
411: }
412:
413: public void init(DeploymentInfo di) throws DeploymentException {
414: log.debug("init, " + di.shortName);
415: try {
416: if (di.url.getProtocol().equalsIgnoreCase("file")) {
417: File file = new File(di.url.getFile());
418:
419: if (!file.isDirectory()) {
420: // If not directory we watch the package
421: di.watch = di.url;
422: } else {
423: // If directory we watch the xml files
424: di.watch = new URL(di.url, "META-INF/ejb-jar.xml");
425: }
426: } else {
427: // We watch the top only, no directory support
428: di.watch = di.url;
429: }
430:
431: // Check for a loader-repository
432: XmlFileLoader xfl = new XmlFileLoader();
433: InputStream in = di.localCl
434: .getResourceAsStream("META-INF/jboss.xml");
435: if (in != null) {
436: try {
437: Element jboss = xfl.getDocument(in,
438: "META-INF/jboss.xml").getDocumentElement();
439: // Check for a ejb level class loading config
440: Element loader = MetaData.getOptionalChild(jboss,
441: "loader-repository");
442: if (loader != null) {
443: LoaderRepositoryFactory.LoaderRepositoryConfig config = LoaderRepositoryFactory
444: .parseRepositoryConfig(loader);
445: di.setRepositoryInfo(config);
446: }
447: } finally {
448: in.close();
449: }
450: }
451: } catch (Exception e) {
452: if (e instanceof DeploymentException)
453: throw (DeploymentException) e;
454: throw new DeploymentException("failed to initialize", e);
455: }
456:
457: // invoke super-class initialization
458: super .init(di);
459: }
460:
461: /**
462: * This is here as a reminder that we may not want to allow ejb jars to
463: * have arbitrary sub deployments. Currently we do.
464: * @param di
465: * @throws DeploymentException
466: */
467: protected void processNestedDeployments(DeploymentInfo di)
468: throws DeploymentException {
469: super .processNestedDeployments(di);
470: }
471:
472: public synchronized void create(DeploymentInfo di)
473: throws DeploymentException {
474: log.debug("create, " + di.shortName);
475: ApplicationMetaData ejbMetaData = null;
476: try {
477: // Initialize the annotations classloader
478: URL loaderURL = (di.localUrl != null ? di.localUrl : di.url);
479: di.annotationsCl = new URLClassLoader(
480: new URL[] { loaderURL }, di.ucl);
481:
482: // Create a file loader with which to load the files
483: XmlFileLoader efm = new XmlFileLoader(validateDTDs);
484: efm.setClassLoader(di.localCl);
485:
486: // redirect to alternative DD
487: URL alternativeDD = null;
488: if (di.alternativeDD != null) {
489: File contentsDir = new File(di.url.getPath())
490: .getParentFile();
491: alternativeDD = new File(contentsDir, di.alternativeDD)
492: .toURL();
493: }
494:
495: // Load XML
496: di.metaData = ejbMetaData = efm.load(alternativeDD);
497:
498: // inherit the security setup from jboss-app.xml
499: if (di.parent != null
500: && di.parent.metaData instanceof J2eeApplicationMetaData) {
501: J2eeApplicationMetaData appMetaData = (J2eeApplicationMetaData) di.parent.metaData;
502:
503: if (ejbMetaData.getSecurityDomain() == null)
504: ejbMetaData.setSecurityDomain(appMetaData
505: .getSecurityDomain());
506:
507: if (ejbMetaData.getUnauthenticatedPrincipal() == null)
508: ejbMetaData.setUnauthenticatedPrincipal(appMetaData
509: .getUnauthenticatedPrincipal());
510:
511: ejbMetaData.getAssemblyDescriptor().mergeSecurityRoles(
512: appMetaData.getSecurityRoles());
513: }
514: } catch (Exception e) {
515: if (e instanceof DeploymentException)
516: throw (DeploymentException) e;
517: throw new DeploymentException("Failed to load metaData", e);
518: }
519:
520: if (verifyDeployments) {
521: // we have a positive attitude
522: boolean allOK = true;
523:
524: // wrapping this into a try - catch block to prevent errors in
525: // verifier from stopping the deployment
526: try {
527: BeanVerifier verifier = new BeanVerifier();
528:
529: // add a listener so we can log the results
530: verifier
531: .addVerificationListener(new VerificationListener() {
532: Logger verifierLog = Logger.getLogger(
533: EJBDeployer.class, "verifier");
534:
535: public void beanChecked(
536: VerificationEvent event) {
537: verifierLog.debug("Bean checked: "
538: + event.getMessage());
539: }
540:
541: public void specViolation(
542: VerificationEvent event) {
543: verifierLog.warn("EJB spec violation: "
544: + (verifierVerbose ? event
545: .getVerbose() : event
546: .getMessage()));
547: }
548: });
549:
550: log.debug("Verifying " + di.url);
551: verifier.verify(di.url,
552: (ApplicationMetaData) di.metaData, di.ucl);
553:
554: allOK = verifier.getSuccess();
555: } catch (Throwable t) {
556: log.warn("Verify failed; continuing", t);
557: allOK = false;
558: }
559:
560: // If the verifier is in strict mode and an error/warning
561: // was found in the Verification process, throw a Deployment
562: // Exception
563: if (strictVerifier && !allOK) {
564: throw new DeploymentException(
565: "Verification of Enterprise "
566: + "Beans failed, see above for error messages.");
567: }
568:
569: }
570:
571: // Create an MBean for the EJB module
572: try {
573: EjbModule ejbModule = new EjbModule(di, tm, webServiceName);
574: String name = ejbMetaData.getJmxName();
575: if (name == null) {
576: name = EjbModule.BASE_EJB_MODULE_NAME + ",module="
577: + di.shortName;
578: }
579: // Build an escaped JMX name including deployment shortname
580: ObjectName ejbModuleName = ObjectNameConverter
581: .convert(name);
582: // Check that the name is not registered
583: if (server.isRegistered(ejbModuleName) == true) {
584: log.debug("The EJBModule name: " + ejbModuleName
585: + "is already registered, adding uid="
586: + System.identityHashCode(ejbModule));
587: name = name + ",uid="
588: + System.identityHashCode(ejbModule);
589: ejbModuleName = ObjectNameConverter.convert(name);
590: }
591:
592: server.registerMBean(ejbModule, ejbModuleName);
593: di.deployedObject = ejbModuleName;
594:
595: log.debug("Deploying: " + di.url);
596: // Invoke the create life cycle method
597: serviceController.create(di.deployedObject);
598: } catch (Exception e) {
599: throw new DeploymentException(
600: "Error during create of EjbModule: " + di.url, e);
601: }
602: super .create(di);
603: }
604:
605: public synchronized void start(DeploymentInfo di)
606: throws DeploymentException {
607: try {
608: // Start application
609: log
610: .debug("start application, deploymentInfo: "
611: + di
612: + ", short name: "
613: + di.shortName
614: + ", parent short name: "
615: + (di.parent == null ? "null"
616: : di.parent.shortName));
617:
618: serviceController.start(di.deployedObject);
619:
620: log.info("Deployed: " + di.url);
621:
622: // Register deployment. Use the application name in the hashtable
623: // FIXME: this is obsolete!! (really?!)
624: deployments.put(di.url, di);
625: } catch (Exception e) {
626: stop(di);
627: destroy(di);
628:
629: throw new DeploymentException("Could not deploy " + di.url,
630: e);
631: }
632: super .start(di);
633: }
634:
635: public void stop(DeploymentInfo di) throws DeploymentException {
636: log.info("Undeploying: " + di.url);
637:
638: try {
639: if (di.deployedObject != null)
640: serviceController.stop(di.deployedObject);
641: } catch (Exception e) {
642: throw new DeploymentException(
643: "problem stopping ejb module: " + di.url, e);
644: }
645: super .stop(di);
646: }
647:
648: public void destroy(DeploymentInfo di) throws DeploymentException {
649: // FIXME: If the put() is obsolete above, this is obsolete, too
650: deployments.remove(di.url);
651:
652: try {
653: if (di.deployedObject != null) {
654: serviceController.destroy(di.deployedObject);
655: serviceController.remove(di.deployedObject);
656: }
657: } catch (Exception e) {
658: throw new DeploymentException(
659: "problem destroying ejb module: " + di.url, e);
660: }
661: super.destroy(di);
662: }
663: }
|