001: /**
002: * EasyBeans
003: * Copyright (C) 2007 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: JOnASDeployer.java 1335 2007-04-24 12:47:31Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.deployer;
025:
026: import java.io.File;
027: import java.io.IOException;
028: import java.lang.reflect.Method;
029: import java.net.URL;
030: import java.util.List;
031: import java.util.Set;
032: import java.util.jar.JarFile;
033:
034: import javax.management.AttributeNotFoundException;
035: import javax.management.InstanceNotFoundException;
036: import javax.management.MBeanException;
037: import javax.management.MalformedObjectNameException;
038: import javax.management.ObjectName;
039: import javax.management.ReflectionException;
040:
041: import org.ow2.easybeans.jmx.CommonsModelerException;
042: import org.ow2.easybeans.jmx.CommonsModelerHelper;
043: import org.ow2.easybeans.jmx.JMXRemoteException;
044: import org.ow2.easybeans.jmx.MBeanServerHelper;
045: import org.ow2.easybeans.util.files.FileUtils;
046: import org.ow2.easybeans.util.files.FileUtilsException;
047: import org.ow2.easybeans.util.url.URLUtils;
048: import org.ow2.util.ee.deploy.api.archive.ArchiveException;
049: import org.ow2.util.ee.deploy.api.deployable.EARDeployable;
050: import org.ow2.util.ee.deploy.api.deployable.EJBDeployable;
051: import org.ow2.util.ee.deploy.api.deployable.IDeployable;
052: import org.ow2.util.ee.deploy.api.deployable.WARDeployable;
053: import org.ow2.util.ee.deploy.api.deployer.DeployerException;
054: import org.ow2.util.ee.deploy.api.deployer.IDeployer;
055: import org.ow2.util.log.Log;
056: import org.ow2.util.log.LogFactory;
057:
058: /**
059: * Implementation of the Deployer for EasyBeans in Tomcat. <br />
060: * It will deploy EJB3 and EAR. EJB3 will be deployed in EasyBeans while WAR
061: * file will go in Tomcat.
062: * @author Florent Benoit
063: */
064: public class TomcatDeployer extends AbsWebContainerDeployer implements
065: IDeployer {
066:
067: /**
068: * Logger.
069: */
070: private static Log logger = LogFactory.getLog(TomcatDeployer.class);
071:
072: /**
073: * Name of the Standard Context class.
074: */
075: private static final String CATALINA_CONTEXT_CLASSNAME = "org.apache.catalina.core.StandardContext";
076:
077: /**
078: * Class object for the catalina context.
079: */
080: private Class catalinaContextClass = null;
081:
082: /**
083: * Name of the start method for the context object.
084: */
085: private static final String START_METHOD_NAME = "start";
086:
087: /**
088: * Method for the start of the catalina context.
089: */
090: private Method startContextMethod = null;
091:
092: /**
093: * Method name for setting the parent class loader on the context.
094: */
095: private static final String SET_PARENT_CLASSLOADER_METHOD_NAME = "setParentClassLoader";
096:
097: /**
098: * Method for setting the parent class loader on the context.
099: */
100: private Method setParentClassLoaderMethod = null;
101:
102: /**
103: * Method name for setting the URL to the war file.
104: */
105: private static final String SET_DOC_BASE_METHOD_NAME = "setDocBase";
106:
107: /**
108: * Method for setting the URL to the doc base.
109: */
110: private Method setDocBaseMethod = null;
111:
112: /**
113: * Method name for setting the context-root of this context.
114: */
115: private static final String SET_PATH_METHOD_NAME = "setPath";
116:
117: /**
118: * Method for setting the context-root of this context.
119: */
120: private Method setPathMethod = null;
121:
122: /**
123: * Method name for setting the default context xml file.
124: */
125: private static final String SET_DEFAULT_CONTEXT_XML_METHOD_NAME = "setDefaultContextXml";
126:
127: /**
128: * Method for setting the default context xml file.
129: */
130: private Method setDefaultContextXmlMethod = null;
131:
132: /**
133: * Method name for setting the default web xml file.
134: */
135: private static final String SET_DEFAULT_WEB_XML_METHOD_NAME = "setDefaultWebXml";
136:
137: /**
138: * Name of the method for changing the Java Delegation model.
139: */
140: private static final String SET_JAVA_DELEGATION_MODEL_METHOD_NAME = "setDelegate";
141:
142: /**
143: * Name of the method for setting the META-INF/context.xml file.
144: */
145: private static final String SET_CONFIG_FILE_METHOD_NAME = "setConfigFile";
146:
147: /**
148: * Method object used for changing the Java Delegation model.
149: */
150: private Method setJavaDelegationModelMethod = null;
151:
152: /**
153: * Method for setting the default web xml file.
154: */
155: private Method setDefaultWebXmlMethod = null;
156:
157: /**
158: * Method for setting the path to META-INF/context.xml file.
159: */
160: private Method setConfigFileMethod = null;
161:
162: /**
163: * Engine Object Name.
164: */
165: private static final String ENGINE_OBJECT_NAME = "*:type=Engine";
166:
167: /**
168: * Destroy operation on the Tomcat context to undeploy the war.
169: */
170: private static final String DESTROY_OPERATION = "destroy";
171:
172: /**
173: * Build a new instance of this deployer.
174: * @throws DeployerException if the instance is not built.
175: */
176: public TomcatDeployer() throws DeployerException {
177: super ();
178:
179: // First, load the context class
180: catalinaContextClass = loadClass(CATALINA_CONTEXT_CLASSNAME);
181:
182: // get methods for the context
183: startContextMethod = getMethod(catalinaContextClass,
184: START_METHOD_NAME);
185: setParentClassLoaderMethod = getMethod(catalinaContextClass,
186: SET_PARENT_CLASSLOADER_METHOD_NAME, ClassLoader.class);
187: setDocBaseMethod = getMethod(catalinaContextClass,
188: SET_DOC_BASE_METHOD_NAME, String.class);
189: setPathMethod = getMethod(catalinaContextClass,
190: SET_PATH_METHOD_NAME, String.class);
191: setDefaultContextXmlMethod = getMethod(catalinaContextClass,
192: SET_DEFAULT_CONTEXT_XML_METHOD_NAME, String.class);
193: setDefaultWebXmlMethod = getMethod(catalinaContextClass,
194: SET_DEFAULT_WEB_XML_METHOD_NAME, String.class);
195: setJavaDelegationModelMethod = getMethod(catalinaContextClass,
196: SET_JAVA_DELEGATION_MODEL_METHOD_NAME, boolean.class);
197: setConfigFileMethod = getMethod(catalinaContextClass,
198: SET_CONFIG_FILE_METHOD_NAME, String.class);
199: }
200:
201: /**
202: * Deploy a deployable. It can be an EJB jar, EAR, WAR, etc.
203: * @param deployable a given deployable
204: * @throws DeployerException if the deployment is not done.
205: */
206: public void deploy(final IDeployable deployable)
207: throws DeployerException {
208: checkSupportedDeployable(deployable);
209: if (deployable instanceof EJBDeployable) {
210: deployEJB((EJBDeployable) deployable);
211: } else if (deployable instanceof EARDeployable) {
212: // needs to unpack it before deploying it
213: EARDeployable earDeployable = unpackEARDeployable((EARDeployable) deployable);
214: deployEAR(earDeployable);
215: }
216: }
217:
218: /**
219: * Deploy the WAR files present in the given EAR.
220: * @param earDeployable the EAR containing the WARs
221: * @param earURL the EAR URL
222: * @param earClassLoader the EAR classloader
223: * @param parentClassLoader the parent classloader (EJB) to use
224: * @throws DeployerException if the wars are not deployed.
225: */
226: @Override
227: protected void deployWARs(final EARDeployable earDeployable,
228: final URL earURL, final ClassLoader earClassLoader,
229: final ClassLoader parentClassLoader)
230: throws DeployerException {
231: // First, try to see if there are .war in this EAR
232: List<WARDeployable> wars = earDeployable.getWARDeployables();
233:
234: for (WARDeployable war : wars) {
235:
236: // Build a new context for this war file
237: Object ctx = newInstance(catalinaContextClass);
238:
239: // Build Object Name
240: String objectName = buildObjectName(war);
241:
242: // Register Object MBean
243: try {
244: CommonsModelerHelper.registerModelerMBean(ctx,
245: objectName);
246: } catch (CommonsModelerException e) {
247: throw new DeployerException(
248: "Cannot register the object '" + ctx
249: + "' with the objectname '"
250: + objectName + "'.", e);
251: }
252:
253: // set the context-root
254: invoke(setPathMethod, ctx, war.getContextRoot());
255:
256: // Get the URL for this War
257: URL warURL = null;
258: try {
259: warURL = war.getArchive().getURL();
260: } catch (ArchiveException e) {
261: throw new DeployerException(
262: "Cannot get the URL for the archive '"
263: + war.getArchive() + "'.", e);
264: }
265:
266: // File of this war
267: File warFile = URLUtils.urlToFile(warURL);
268:
269: // docbase
270: String docBase = warFile.getPath();
271:
272: // File and not a directory --> unpack it
273: if (warFile.isFile()) {
274: // Need to unpack the file
275: File unpackDir = unpack(warFile, war, earURL);
276: docBase = unpackDir.getPath();
277: }
278:
279: // set the path to the war file (needs to be unpacked else it will
280: // be unpacked by tomcat in the webapps folder and it will try to
281: // deploy twice the webapp with wrong classloader)
282: invoke(setDocBaseMethod, ctx, docBase);
283:
284: // default XML files
285: invoke(setDefaultWebXmlMethod, ctx, "conf/web.xml");
286: invoke(setDefaultContextXmlMethod, ctx, "context.xml");
287:
288: File contextXmlFile = new File(docBase + File.separator
289: + "META-INF" + File.separator + "context.xml");
290: // META-INF/context.xml file support
291: if (contextXmlFile.exists()) {
292: invoke(setConfigFileMethod, ctx, contextXmlFile
293: .getAbsolutePath());
294: }
295:
296: // Set the parent class loader
297: invoke(setParentClassLoaderMethod, ctx, parentClassLoader);
298:
299: // Set the java delegation model
300: invoke(setJavaDelegationModelMethod, ctx, Boolean.TRUE);
301:
302: // Invoke init method
303: // invoke(initContextMethod, ctx);
304:
305: // Invoke start method
306: invoke(startContextMethod, ctx);
307:
308: // War has been deployed
309: logger
310: .info(
311: "The war ''{0}'' has been deployed on the ''{1}'' context.",
312: war, war.getContextRoot());
313: }
314: }
315:
316: /**
317: * Check that the given deployable is supported by this deployer. If it is
318: * not supported, throw an error.
319: * @param deployable the deployable that needs to be deployed
320: * @throws DeployerException if this deployable is not supported.
321: */
322: private void checkSupportedDeployable(final IDeployable deployable)
323: throws DeployerException {
324: if (!(deployable instanceof EARDeployable || deployable instanceof EJBDeployable)) {
325: throw new DeployerException("The deployable '" + deployable
326: + "' is not supported by this deployer");
327: }
328: }
329:
330: /**
331: * Undeploy an given WAR (called by the undeploy method).
332: * @param warDeployable a given WAR deployable
333: * @throws DeployerException if the undeployment is not done.
334: */
335: @Override
336: protected void undeployWAR(final WARDeployable warDeployable)
337: throws DeployerException {
338: // get the root context of this deployable
339: String contextRoot = warDeployable.getContextRoot();
340:
341: // Now, search the MBean of this context
342: ObjectName contextObjectName = null;
343: try {
344: contextObjectName = new ObjectName(
345: buildObjectName(warDeployable));
346: } catch (MalformedObjectNameException e) {
347: throw new DeployerException(
348: "Cannot get the ObjectName for the WAR deployable '"
349: + warDeployable + "'.", e);
350: } catch (NullPointerException e) {
351: throw new DeployerException(
352: "Cannot get the ObjectName for the WAR deployable '"
353: + warDeployable + "'.", e);
354: }
355:
356: // Now, search if the MBean of this context is present
357: try {
358: if (!MBeanServerHelper.getMBeanServerServer().isRegistered(
359: contextObjectName)) {
360: throw new DeployerException(
361: "There is no MBean with the ObjectName '"
362: + contextObjectName
363: + "' in the MBean Server for the WAR deployable '"
364: + warDeployable + "'.");
365: }
366: } catch (JMXRemoteException e) {
367: throw new DeployerException(
368: "Cannot check if the MBean with the ObjectName '"
369: + contextObjectName
370: + "'is registered in the MBean Server for the WAR deployable '"
371: + warDeployable + "'.");
372: }
373:
374: // Undeploy
375: try {
376: MBeanServerHelper.getMBeanServerServer().invoke(
377: contextObjectName, DESTROY_OPERATION, null, null);
378: } catch (InstanceNotFoundException e) {
379: throw new DeployerException("Cannot remove the context '"
380: + contextRoot + "' of the war deployable '"
381: + warDeployable + "'.", e);
382: } catch (MBeanException e) {
383: throw new DeployerException("Cannot remove the context '"
384: + contextRoot + "' of the war deployable '"
385: + warDeployable + "'.", e);
386: } catch (ReflectionException e) {
387: throw new DeployerException("Cannot remove the context '"
388: + contextRoot + "' of the war deployable '"
389: + warDeployable + "'.", e);
390: } catch (JMXRemoteException e) {
391: throw new DeployerException("Cannot remove the context '"
392: + contextRoot + "' of the war deployable '"
393: + warDeployable + "'.", e);
394: }
395:
396: logger
397: .info(
398: "The context ''{0}'' of the War ''{1}'' has been undeployed",
399: contextRoot, warDeployable);
400: }
401:
402: /**
403: * Build an objectname for the given war.
404: * @param war the given war deployable that contains the datas.
405: * @return a JMX object name.
406: * @throws DeployerException if the object name cannot be built.
407: */
408: private String buildObjectName(final WARDeployable war)
409: throws DeployerException {
410: String objectName = getDomain() + ":j2eeType=WebModule,name=//"
411: + getDefaultHost() + "/" + war.getContextRoot()
412: + ",J2EEServer=EasyBeans,J2EEApplication=EAR";
413: return objectName;
414: }
415:
416: /**
417: * Gets the JMX domain of Tomcat.
418: * @return the JMX domain of Tomcat.
419: * @throws DeployerException if the domain cannot be found
420: */
421: private String getDomain() throws DeployerException {
422: return getEngineObjectName().getDomain();
423: }
424:
425: /**
426: * @return the first Engine object name found in the MBean server
427: * @throws DeployerException if the engine object name is not found in
428: * the MBean server.
429: */
430: private ObjectName getEngineObjectName() throws DeployerException {
431: // Build Engine object name
432: ObjectName engineObjectName = null;
433: try {
434: engineObjectName = new ObjectName(ENGINE_OBJECT_NAME);
435: } catch (MalformedObjectNameException e) {
436: throw new DeployerException(
437: "Cannot build Tomcat Engine MBean.", e);
438: } catch (NullPointerException e) {
439: throw new DeployerException(
440: "Cannot build Tomcat Engine MBean.", e);
441: }
442:
443: // Ask the MBean server
444: Set objectNames = null;
445: try {
446: // Get the list
447: objectNames = MBeanServerHelper.getMBeanServerServer()
448: .queryNames(engineObjectName, null);
449: } catch (JMXRemoteException e) {
450: throw new DeployerException(
451: "Cannot get Tomcat Engine MBean.", e);
452: }
453:
454: // Find objects ?
455: if (objectNames.size() == 0) {
456: throw new DeployerException(
457: "No Tomcat Engine MBean was found in the MBean server");
458: }
459:
460: // Return the domain of this object name
461: return ((ObjectName) objectNames.iterator().next());
462: }
463:
464: /**
465: * @return the default host of the first engine found in the MBean server
466: * @throws DeployerException if the MBean is not found.
467: */
468: private String getDefaultHost() throws DeployerException {
469: ObjectName engineObjectName = getEngineObjectName();
470: try {
471: return MBeanServerHelper.getMBeanServerServer()
472: .getAttribute(engineObjectName, "defaultHost")
473: .toString();
474: } catch (AttributeNotFoundException e) {
475: throw new DeployerException(
476: "Cannot get the default host on the object name '"
477: + engineObjectName + "'.", e);
478: } catch (InstanceNotFoundException e) {
479: throw new DeployerException(
480: "Cannot get the default host on the object name '"
481: + engineObjectName + "'.", e);
482: } catch (MBeanException e) {
483: throw new DeployerException(
484: "Cannot get the default host on the object name '"
485: + engineObjectName + "'.", e);
486: } catch (ReflectionException e) {
487: throw new DeployerException(
488: "Cannot get the default host on the object name '"
489: + engineObjectName + "'.", e);
490: } catch (JMXRemoteException e) {
491: throw new DeployerException(
492: "Cannot get the default host on the object name '"
493: + engineObjectName + "'.", e);
494: }
495: }
496:
497: /**
498: * Unpack the given war into a temp directory.
499: * @param warFile the war to unpack
500: * @param war data on the given war
501: * @param earURL url of the EAR that contains the war
502: * @return the File to the unpack directory
503: * @throws DeployerException if unpack fails
504: */
505: private File unpack(final File warFile, final WARDeployable war,
506: final URL earURL) throws DeployerException {
507: // Get EAR application name
508: String earName = URLUtils.urlToFile(earURL).getName();
509:
510: // unpack Root directory
511: String rootUnpackDir = System.getProperty("java.io.tmpdir")
512: + File.separator + System.getProperty("user.name")
513: + "-EasyBeans-unpack" + File.separator;
514:
515: // Unpack directory
516: File unpackDir = new File(rootUnpackDir, earName
517: + File.separator + warFile.getName());
518:
519: // Build a JarFile on the war
520: JarFile packedJar;
521: try {
522: packedJar = new JarFile(warFile);
523: } catch (IOException e) {
524: throw new DeployerException("The war file '" + warFile
525: + "' is not a valid jar file", e);
526: }
527:
528: // Unpack the war
529: try {
530: FileUtils.unpack(packedJar, unpackDir);
531: } catch (FileUtilsException e) {
532: throw new DeployerException("Cannot unpack the file '"
533: + packedJar + "' in the directory '" + unpackDir
534: + "'.", e);
535: }
536:
537: return unpackDir;
538:
539: }
540:
541: }
|