0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.startup;
0019:
0020: import java.io.BufferedOutputStream;
0021: import java.io.File;
0022: import java.io.FileOutputStream;
0023: import java.io.IOException;
0024: import java.io.InputStream;
0025: import java.util.ArrayList;
0026: import java.util.HashMap;
0027: import java.util.LinkedHashMap;
0028: import java.util.jar.JarEntry;
0029: import java.util.jar.JarFile;
0030:
0031: import javax.management.ObjectName;
0032:
0033: import org.apache.catalina.Container;
0034: import org.apache.catalina.Context;
0035: import org.apache.catalina.Engine;
0036: import org.apache.catalina.Host;
0037: import org.apache.catalina.Lifecycle;
0038: import org.apache.catalina.LifecycleEvent;
0039: import org.apache.catalina.LifecycleListener;
0040: import org.apache.catalina.core.ContainerBase;
0041: import org.apache.catalina.core.StandardHost;
0042: import org.apache.catalina.util.StringManager;
0043: import org.apache.tomcat.util.digester.Digester;
0044: import org.apache.tomcat.util.modeler.Registry;
0045:
0046: /**
0047: * Startup event listener for a <b>Host</b> that configures the properties
0048: * of that Host, and the associated defined contexts.
0049: *
0050: * @author Craig R. McClanahan
0051: * @author Remy Maucherat
0052: * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
0053: */
0054: public class HostConfig implements LifecycleListener {
0055:
0056: protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
0057: .getLog(HostConfig.class);
0058:
0059: // ----------------------------------------------------- Instance Variables
0060:
0061: /**
0062: * App base.
0063: */
0064: protected File appBase = null;
0065:
0066: /**
0067: * Config base.
0068: */
0069: protected File configBase = null;
0070:
0071: /**
0072: * The Java class name of the Context configuration class we should use.
0073: */
0074: protected String configClass = "org.apache.catalina.startup.ContextConfig";
0075:
0076: /**
0077: * The Java class name of the Context implementation we should use.
0078: */
0079: protected String contextClass = "org.apache.catalina.core.StandardContext";
0080:
0081: /**
0082: * The Host we are associated with.
0083: */
0084: protected Host host = null;
0085:
0086: /**
0087: * The JMX ObjectName of this component.
0088: */
0089: protected ObjectName oname = null;
0090:
0091: /**
0092: * The string resources for this package.
0093: */
0094: protected static final StringManager sm = StringManager
0095: .getManager(Constants.Package);
0096:
0097: /**
0098: * Should we deploy XML Context config files?
0099: */
0100: protected boolean deployXML = false;
0101:
0102: /**
0103: * Should we unpack WAR files when auto-deploying applications in the
0104: * <code>appBase</code> directory?
0105: */
0106: protected boolean unpackWARs = false;
0107:
0108: /**
0109: * Map of deployed applications.
0110: */
0111: protected HashMap deployed = new HashMap();
0112:
0113: /**
0114: * List of applications which are being serviced, and shouldn't be
0115: * deployed/undeployed/redeployed at the moment.
0116: */
0117: protected ArrayList serviced = new ArrayList();
0118:
0119: /**
0120: * Attribute value used to turn on/off XML validation
0121: */
0122: protected boolean xmlValidation = false;
0123:
0124: /**
0125: * Attribute value used to turn on/off XML namespace awarenes.
0126: */
0127: protected boolean xmlNamespaceAware = false;
0128:
0129: /**
0130: * The <code>Digester</code> instance used to parse context descriptors.
0131: */
0132: protected static Digester digester = createDigester();
0133:
0134: // ------------------------------------------------------------- Properties
0135:
0136: /**
0137: * Return the Context configuration class name.
0138: */
0139: public String getConfigClass() {
0140:
0141: return (this .configClass);
0142:
0143: }
0144:
0145: /**
0146: * Set the Context configuration class name.
0147: *
0148: * @param configClass The new Context configuration class name.
0149: */
0150: public void setConfigClass(String configClass) {
0151:
0152: this .configClass = configClass;
0153:
0154: }
0155:
0156: /**
0157: * Return the Context implementation class name.
0158: */
0159: public String getContextClass() {
0160:
0161: return (this .contextClass);
0162:
0163: }
0164:
0165: /**
0166: * Set the Context implementation class name.
0167: *
0168: * @param contextClass The new Context implementation class name.
0169: */
0170: public void setContextClass(String contextClass) {
0171:
0172: this .contextClass = contextClass;
0173:
0174: }
0175:
0176: /**
0177: * Return the deploy XML config file flag for this component.
0178: */
0179: public boolean isDeployXML() {
0180:
0181: return (this .deployXML);
0182:
0183: }
0184:
0185: /**
0186: * Set the deploy XML config file flag for this component.
0187: *
0188: * @param deployXML The new deploy XML flag
0189: */
0190: public void setDeployXML(boolean deployXML) {
0191:
0192: this .deployXML = deployXML;
0193:
0194: }
0195:
0196: /**
0197: * Return the unpack WARs flag.
0198: */
0199: public boolean isUnpackWARs() {
0200:
0201: return (this .unpackWARs);
0202:
0203: }
0204:
0205: /**
0206: * Set the unpack WARs flag.
0207: *
0208: * @param unpackWARs The new unpack WARs flag
0209: */
0210: public void setUnpackWARs(boolean unpackWARs) {
0211:
0212: this .unpackWARs = unpackWARs;
0213:
0214: }
0215:
0216: /**
0217: * Set the validation feature of the XML parser used when
0218: * parsing xml instances.
0219: * @param xmlValidation true to enable xml instance validation
0220: */
0221: public void setXmlValidation(boolean xmlValidation) {
0222: this .xmlValidation = xmlValidation;
0223: }
0224:
0225: /**
0226: * Get the server.xml <host> attribute's xmlValidation.
0227: * @return true if validation is enabled.
0228: *
0229: */
0230: public boolean getXmlValidation() {
0231: return xmlValidation;
0232: }
0233:
0234: /**
0235: * Get the server.xml <host> attribute's xmlNamespaceAware.
0236: * @return true if namespace awarenes is enabled.
0237: *
0238: */
0239: public boolean getXmlNamespaceAware() {
0240: return xmlNamespaceAware;
0241: }
0242:
0243: /**
0244: * Set the namespace aware feature of the XML parser used when
0245: * parsing xml instances.
0246: * @param xmlNamespaceAware true to enable namespace awareness
0247: */
0248: public void setXmlNamespaceAware(boolean xmlNamespaceAware) {
0249: this .xmlNamespaceAware = xmlNamespaceAware;
0250: }
0251:
0252: // --------------------------------------------------------- Public Methods
0253:
0254: /**
0255: * Process the START event for an associated Host.
0256: *
0257: * @param event The lifecycle event that has occurred
0258: */
0259: public void lifecycleEvent(LifecycleEvent event) {
0260:
0261: if (event.getType().equals(Lifecycle.PERIODIC_EVENT))
0262: check();
0263:
0264: // Identify the host we are associated with
0265: try {
0266: host = (Host) event.getLifecycle();
0267: if (host instanceof StandardHost) {
0268: setDeployXML(((StandardHost) host).isDeployXML());
0269: setUnpackWARs(((StandardHost) host).isUnpackWARs());
0270: setXmlNamespaceAware(((StandardHost) host)
0271: .getXmlNamespaceAware());
0272: setXmlValidation(((StandardHost) host)
0273: .getXmlValidation());
0274: }
0275: } catch (ClassCastException e) {
0276: log.error(sm.getString("hostConfig.cce", event
0277: .getLifecycle()), e);
0278: return;
0279: }
0280:
0281: // Process the event that has occurred
0282: if (event.getType().equals(Lifecycle.START_EVENT))
0283: start();
0284: else if (event.getType().equals(Lifecycle.STOP_EVENT))
0285: stop();
0286:
0287: }
0288:
0289: /**
0290: * Add a serviced application to the list.
0291: */
0292: public synchronized void addServiced(String name) {
0293: serviced.add(name);
0294: }
0295:
0296: /**
0297: * Is application serviced ?
0298: * @return state of the application
0299: */
0300: public synchronized boolean isServiced(String name) {
0301: return (serviced.contains(name));
0302: }
0303:
0304: /**
0305: * Removed a serviced application from the list.
0306: */
0307: public synchronized void removeServiced(String name) {
0308: serviced.remove(name);
0309: }
0310:
0311: /**
0312: * Get the instant where an application was deployed.
0313: * @return 0L if no application with that name is deployed, or the instant
0314: * on which the application was deployed
0315: */
0316: public long getDeploymentTime(String name) {
0317: DeployedApplication app = (DeployedApplication) deployed
0318: .get(name);
0319: if (app == null) {
0320: return 0L;
0321: } else {
0322: return app.timestamp;
0323: }
0324: }
0325:
0326: /**
0327: * Has the specified application been deployed? Note applications defined
0328: * in server.xml will not have been deployed.
0329: * @return <code>true</code> if the application has been deployed and
0330: * <code>false</code> if the applciation has not been deployed or does not
0331: * exist
0332: */
0333: public boolean isDeployed(String name) {
0334: DeployedApplication app = (DeployedApplication) deployed
0335: .get(name);
0336: if (app == null) {
0337: return false;
0338: } else {
0339: return true;
0340: }
0341: }
0342:
0343: // ------------------------------------------------------ Protected Methods
0344:
0345: /**
0346: * Create the digester which will be used to parse context config files.
0347: */
0348: protected static Digester createDigester() {
0349: Digester digester = new Digester();
0350: digester.setValidating(false);
0351: // Add object creation rule
0352: digester
0353: .addObjectCreate("Context",
0354: "org.apache.catalina.core.StandardContext",
0355: "className");
0356: // Set the properties on that object (it doesn't matter if extra
0357: // properties are set)
0358: digester.addSetProperties("Context");
0359: return (digester);
0360: }
0361:
0362: /**
0363: * Return a File object representing the "application root" directory
0364: * for our associated Host.
0365: */
0366: protected File appBase() {
0367:
0368: if (appBase != null) {
0369: return appBase;
0370: }
0371:
0372: File file = new File(host.getAppBase());
0373: if (!file.isAbsolute())
0374: file = new File(System.getProperty("catalina.base"), host
0375: .getAppBase());
0376: try {
0377: appBase = file.getCanonicalFile();
0378: } catch (IOException e) {
0379: appBase = file;
0380: }
0381: return (appBase);
0382:
0383: }
0384:
0385: /**
0386: * Return a File object representing the "configuration root" directory
0387: * for our associated Host.
0388: */
0389: protected File configBase() {
0390:
0391: if (configBase != null) {
0392: return configBase;
0393: }
0394:
0395: File file = new File(System.getProperty("catalina.base"),
0396: "conf");
0397: Container parent = host.getParent();
0398: if ((parent != null) && (parent instanceof Engine)) {
0399: file = new File(file, parent.getName());
0400: }
0401: file = new File(file, host.getName());
0402: try {
0403: configBase = file.getCanonicalFile();
0404: } catch (IOException e) {
0405: configBase = file;
0406: }
0407: return (configBase);
0408:
0409: }
0410:
0411: /**
0412: * Get the name of the configBase.
0413: * For use with JMX management.
0414: */
0415: public String getConfigBaseName() {
0416: return configBase().getAbsolutePath();
0417: }
0418:
0419: /**
0420: * Given a context path, get the config file name.
0421: */
0422: protected String getConfigFile(String path) {
0423: String basename = null;
0424: if (path.equals("")) {
0425: basename = "ROOT";
0426: } else {
0427: basename = path.substring(1).replace('/', '#');
0428: }
0429: return (basename);
0430: }
0431:
0432: /**
0433: * Given a context path, get the config file name.
0434: */
0435: protected String getDocBase(String path) {
0436: String basename = null;
0437: if (path.equals("")) {
0438: basename = "ROOT";
0439: } else {
0440: basename = path.substring(1);
0441: }
0442: return (basename);
0443: }
0444:
0445: /**
0446: * Deploy applications for any directories or WAR files that are found
0447: * in our "application root" directory.
0448: */
0449: protected void deployApps() {
0450:
0451: File appBase = appBase();
0452: File configBase = configBase();
0453: // Deploy XML descriptors from configBase
0454: deployDescriptors(configBase, configBase.list());
0455: // Deploy WARs, and loop if additional descriptors are found
0456: deployWARs(appBase, appBase.list());
0457: // Deploy expanded folders
0458: deployDirectories(appBase, appBase.list());
0459:
0460: }
0461:
0462: /**
0463: * Deploy applications for any directories or WAR files that are found
0464: * in our "application root" directory.
0465: */
0466: protected void deployApps(String name) {
0467:
0468: File appBase = appBase();
0469: File configBase = configBase();
0470: String baseName = getConfigFile(name);
0471: String docBase = getConfigFile(name);
0472:
0473: // Deploy XML descriptors from configBase
0474: File xml = new File(configBase, baseName + ".xml");
0475: if (xml.exists())
0476: deployDescriptor(name, xml, baseName + ".xml");
0477: // Deploy WARs, and loop if additional descriptors are found
0478: File war = new File(appBase, docBase + ".war");
0479: if (war.exists())
0480: deployWAR(name, war, docBase + ".war");
0481: // Deploy expanded folders
0482: File dir = new File(appBase, docBase);
0483: if (dir.exists())
0484: deployDirectory(name, dir, docBase);
0485:
0486: }
0487:
0488: /**
0489: * Deploy XML context descriptors.
0490: */
0491: protected void deployDescriptors(File configBase, String[] files) {
0492:
0493: if (files == null)
0494: return;
0495:
0496: for (int i = 0; i < files.length; i++) {
0497:
0498: if (files[i].equalsIgnoreCase("META-INF"))
0499: continue;
0500: if (files[i].equalsIgnoreCase("WEB-INF"))
0501: continue;
0502: File contextXml = new File(configBase, files[i]);
0503: if (files[i].toLowerCase().endsWith(".xml")) {
0504:
0505: // Calculate the context path and make sure it is unique
0506: String nameTmp = files[i].substring(0, files[i]
0507: .length() - 4);
0508: String contextPath = "/" + nameTmp.replace('#', '/');
0509: if (nameTmp.equals("ROOT")) {
0510: contextPath = "";
0511: }
0512:
0513: if (isServiced(contextPath))
0514: continue;
0515:
0516: String file = files[i];
0517:
0518: deployDescriptor(contextPath, contextXml, file);
0519:
0520: }
0521:
0522: }
0523:
0524: }
0525:
0526: /**
0527: * @param contextPath
0528: * @param contextXml
0529: * @param file
0530: */
0531: protected void deployDescriptor(String contextPath,
0532: File contextXml, String file) {
0533: if (deploymentExists(contextPath)) {
0534: return;
0535: }
0536:
0537: DeployedApplication deployedApp = new DeployedApplication(
0538: contextPath);
0539:
0540: // Assume this is a configuration descriptor and deploy it
0541: if (log.isDebugEnabled()) {
0542: log
0543: .debug(sm.getString("hostConfig.deployDescriptor",
0544: file));
0545: }
0546:
0547: Context context = null;
0548: try {
0549: synchronized (digester) {
0550: try {
0551: context = (Context) digester.parse(contextXml);
0552: if (context == null) {
0553: log.error(sm.getString(
0554: "hostConfig.deployDescriptor.error",
0555: file));
0556: return;
0557: }
0558: } finally {
0559: digester.reset();
0560: }
0561: }
0562: if (context instanceof Lifecycle) {
0563: Class clazz = Class.forName(host.getConfigClass());
0564: LifecycleListener listener = (LifecycleListener) clazz
0565: .newInstance();
0566: ((Lifecycle) context).addLifecycleListener(listener);
0567: }
0568: context.setConfigFile(contextXml.getAbsolutePath());
0569: context.setPath(contextPath);
0570: // Add the associated docBase to the redeployed list if it's a WAR
0571: boolean isWar = false;
0572: boolean isExternal = false;
0573: if (context.getDocBase() != null) {
0574: File docBase = new File(context.getDocBase());
0575: if (!docBase.isAbsolute()) {
0576: docBase = new File(appBase(), context.getDocBase());
0577: }
0578: // If external docBase, register .xml as redeploy first
0579: if (!docBase.getCanonicalPath().startsWith(
0580: appBase().getAbsolutePath())) {
0581: isExternal = true;
0582: deployedApp.redeployResources.put(contextXml
0583: .getAbsolutePath(), new Long(contextXml
0584: .lastModified()));
0585: deployedApp.redeployResources.put(docBase
0586: .getAbsolutePath(), new Long(docBase
0587: .lastModified()));
0588: if (docBase.getAbsolutePath().toLowerCase()
0589: .endsWith(".war")) {
0590: isWar = true;
0591: }
0592: } else {
0593: log
0594: .warn(sm
0595: .getString(
0596: "hostConfig.deployDescriptor.localDocBaseSpecified",
0597: docBase));
0598: // Ignore specified docBase
0599: context.setDocBase(null);
0600: }
0601: }
0602: host.addChild(context);
0603: // Get paths for WAR and expanded WAR in appBase
0604: String name = null;
0605: String path = context.getPath();
0606: if (path.equals("")) {
0607: name = "ROOT";
0608: } else {
0609: if (path.startsWith("/")) {
0610: name = path.substring(1);
0611: } else {
0612: name = path;
0613: }
0614: }
0615: File expandedDocBase = new File(name);
0616: File warDocBase = new File(name + ".war");
0617: if (!expandedDocBase.isAbsolute()) {
0618: expandedDocBase = new File(appBase(), name);
0619: warDocBase = new File(appBase(), name + ".war");
0620: }
0621: // Add the eventual unpacked WAR and all the resources which will be
0622: // watched inside it
0623: if (isWar && unpackWARs) {
0624: deployedApp.redeployResources.put(expandedDocBase
0625: .getAbsolutePath(), new Long(expandedDocBase
0626: .lastModified()));
0627: deployedApp.redeployResources.put(contextXml
0628: .getAbsolutePath(), new Long(contextXml
0629: .lastModified()));
0630: addWatchedResources(deployedApp, expandedDocBase
0631: .getAbsolutePath(), context);
0632: } else {
0633: // Find an existing matching war and expanded folder
0634: if (warDocBase.exists()) {
0635: deployedApp.redeployResources.put(warDocBase
0636: .getAbsolutePath(), new Long(warDocBase
0637: .lastModified()));
0638: }
0639: if (expandedDocBase.exists()) {
0640: deployedApp.redeployResources.put(expandedDocBase
0641: .getAbsolutePath(), new Long(
0642: expandedDocBase.lastModified()));
0643: addWatchedResources(deployedApp, expandedDocBase
0644: .getAbsolutePath(), context);
0645: } else {
0646: addWatchedResources(deployedApp, null, context);
0647: }
0648: // Add the context XML to the list of files which should trigger a redeployment
0649: if (!isExternal) {
0650: deployedApp.redeployResources.put(contextXml
0651: .getAbsolutePath(), new Long(contextXml
0652: .lastModified()));
0653: }
0654: }
0655: } catch (Throwable t) {
0656: log.error(sm.getString("hostConfig.deployDescriptor.error",
0657: file), t);
0658: }
0659:
0660: if (context != null
0661: && host.findChild(context.getName()) != null) {
0662: deployed.put(contextPath, deployedApp);
0663: }
0664: }
0665:
0666: /**
0667: * Deploy WAR files.
0668: */
0669: protected void deployWARs(File appBase, String[] files) {
0670:
0671: if (files == null)
0672: return;
0673:
0674: for (int i = 0; i < files.length; i++) {
0675:
0676: if (files[i].equalsIgnoreCase("META-INF"))
0677: continue;
0678: if (files[i].equalsIgnoreCase("WEB-INF"))
0679: continue;
0680: File dir = new File(appBase, files[i]);
0681: if (files[i].toLowerCase().endsWith(".war")) {
0682:
0683: // Calculate the context path and make sure it is unique
0684: String contextPath = "/" + files[i];
0685: int period = contextPath.lastIndexOf(".");
0686: if (period >= 0)
0687: contextPath = contextPath.substring(0, period);
0688: if (contextPath.equals("/ROOT"))
0689: contextPath = "";
0690:
0691: if (isServiced(contextPath))
0692: continue;
0693:
0694: String file = files[i];
0695:
0696: deployWAR(contextPath, dir, file);
0697:
0698: }
0699:
0700: }
0701:
0702: }
0703:
0704: /**
0705: * @param contextPath
0706: * @param dir
0707: * @param file
0708: */
0709: protected void deployWAR(String contextPath, File dir, String file) {
0710:
0711: if (deploymentExists(contextPath))
0712: return;
0713:
0714: // Checking for a nested /META-INF/context.xml
0715: JarFile jar = null;
0716: JarEntry entry = null;
0717: InputStream istream = null;
0718: BufferedOutputStream ostream = null;
0719: File xml = new File(configBase, file.substring(0, file
0720: .lastIndexOf("."))
0721: + ".xml");
0722: if (deployXML && !xml.exists()) {
0723: try {
0724: jar = new JarFile(dir);
0725: entry = jar
0726: .getJarEntry(Constants.ApplicationContextXml);
0727: if (entry != null) {
0728: istream = jar.getInputStream(entry);
0729:
0730: configBase.mkdirs();
0731:
0732: ostream = new BufferedOutputStream(
0733: new FileOutputStream(xml), 1024);
0734: byte buffer[] = new byte[1024];
0735: while (true) {
0736: int n = istream.read(buffer);
0737: if (n < 0) {
0738: break;
0739: }
0740: ostream.write(buffer, 0, n);
0741: }
0742: ostream.flush();
0743: ostream.close();
0744: ostream = null;
0745: istream.close();
0746: istream = null;
0747: entry = null;
0748: jar.close();
0749: jar = null;
0750: }
0751: } catch (Exception e) {
0752: // Ignore and continue
0753: if (ostream != null) {
0754: try {
0755: ostream.close();
0756: } catch (Throwable t) {
0757: ;
0758: }
0759: ostream = null;
0760: }
0761: if (istream != null) {
0762: try {
0763: istream.close();
0764: } catch (Throwable t) {
0765: ;
0766: }
0767: istream = null;
0768: }
0769: } finally {
0770: entry = null;
0771: if (jar != null) {
0772: try {
0773: jar.close();
0774: } catch (Throwable t) {
0775: ;
0776: }
0777: jar = null;
0778: }
0779: }
0780: }
0781:
0782: DeployedApplication deployedApp = new DeployedApplication(
0783: contextPath);
0784:
0785: // Deploy the application in this WAR file
0786: if (log.isInfoEnabled())
0787: log.info(sm.getString("hostConfig.deployJar", file));
0788:
0789: // Populate redeploy resources with the WAR file
0790: deployedApp.redeployResources.put(dir.getAbsolutePath(),
0791: new Long(dir.lastModified()));
0792:
0793: try {
0794: Context context = (Context) Class.forName(contextClass)
0795: .newInstance();
0796: if (context instanceof Lifecycle) {
0797: Class clazz = Class.forName(host.getConfigClass());
0798: LifecycleListener listener = (LifecycleListener) clazz
0799: .newInstance();
0800: ((Lifecycle) context).addLifecycleListener(listener);
0801: }
0802: context.setPath(contextPath);
0803: context.setDocBase(file);
0804: if (xml.exists()) {
0805: context.setConfigFile(xml.getAbsolutePath());
0806: deployedApp.redeployResources.put(
0807: xml.getAbsolutePath(), new Long(xml
0808: .lastModified()));
0809: }
0810: host.addChild(context);
0811: // If we're unpacking WARs, the docBase will be mutated after
0812: // starting the context
0813: if (unpackWARs && (context.getDocBase() != null)) {
0814: String name = null;
0815: String path = context.getPath();
0816: if (path.equals("")) {
0817: name = "ROOT";
0818: } else {
0819: if (path.startsWith("/")) {
0820: name = path.substring(1);
0821: } else {
0822: name = path;
0823: }
0824: }
0825: File docBase = new File(name);
0826: if (!docBase.isAbsolute()) {
0827: docBase = new File(appBase(), name);
0828: }
0829: deployedApp.redeployResources.put(docBase
0830: .getAbsolutePath(), new Long(docBase
0831: .lastModified()));
0832: addWatchedResources(deployedApp, docBase
0833: .getAbsolutePath(), context);
0834: } else {
0835: addWatchedResources(deployedApp, null, context);
0836: }
0837: } catch (Throwable t) {
0838: log.error(sm.getString("hostConfig.deployJar.error", file),
0839: t);
0840: }
0841:
0842: deployed.put(contextPath, deployedApp);
0843: }
0844:
0845: /**
0846: * Deploy directories.
0847: */
0848: protected void deployDirectories(File appBase, String[] files) {
0849:
0850: if (files == null)
0851: return;
0852:
0853: for (int i = 0; i < files.length; i++) {
0854:
0855: if (files[i].equalsIgnoreCase("META-INF"))
0856: continue;
0857: if (files[i].equalsIgnoreCase("WEB-INF"))
0858: continue;
0859: File dir = new File(appBase, files[i]);
0860: if (dir.isDirectory()) {
0861:
0862: // Calculate the context path and make sure it is unique
0863: String contextPath = "/" + files[i];
0864: if (files[i].equals("ROOT"))
0865: contextPath = "";
0866:
0867: if (isServiced(contextPath))
0868: continue;
0869:
0870: deployDirectory(contextPath, dir, files[i]);
0871:
0872: }
0873:
0874: }
0875:
0876: }
0877:
0878: /**
0879: * @param contextPath
0880: * @param dir
0881: * @param file
0882: */
0883: protected void deployDirectory(String contextPath, File dir,
0884: String file) {
0885: DeployedApplication deployedApp = new DeployedApplication(
0886: contextPath);
0887:
0888: if (deploymentExists(contextPath))
0889: return;
0890:
0891: // Deploy the application in this directory
0892: if (log.isDebugEnabled())
0893: log.debug(sm.getString("hostConfig.deployDir", file));
0894: try {
0895: Context context = (Context) Class.forName(contextClass)
0896: .newInstance();
0897: if (context instanceof Lifecycle) {
0898: Class clazz = Class.forName(host.getConfigClass());
0899: LifecycleListener listener = (LifecycleListener) clazz
0900: .newInstance();
0901: ((Lifecycle) context).addLifecycleListener(listener);
0902: }
0903: context.setPath(contextPath);
0904: context.setDocBase(file);
0905: File configFile = new File(dir,
0906: Constants.ApplicationContextXml);
0907: if (deployXML) {
0908: context.setConfigFile(configFile.getAbsolutePath());
0909: }
0910: host.addChild(context);
0911: deployedApp.redeployResources.put(dir.getAbsolutePath(),
0912: new Long(dir.lastModified()));
0913: if (deployXML) {
0914: deployedApp.redeployResources.put(configFile
0915: .getAbsolutePath(), new Long(configFile
0916: .lastModified()));
0917: }
0918: addWatchedResources(deployedApp, dir.getAbsolutePath(),
0919: context);
0920: } catch (Throwable t) {
0921: log.error(sm.getString("hostConfig.deployDir.error", file),
0922: t);
0923: }
0924:
0925: deployed.put(contextPath, deployedApp);
0926: }
0927:
0928: /**
0929: * Check if a webapp is already deployed in this host.
0930: *
0931: * @param contextPath of the context which will be checked
0932: */
0933: protected boolean deploymentExists(String contextPath) {
0934: return (deployed.containsKey(contextPath) || (host
0935: .findChild(contextPath) != null));
0936: }
0937:
0938: /**
0939: * Add watched resources to the specified Context.
0940: * @param app HostConfig deployed app
0941: * @param docBase web app docBase
0942: * @param context web application context
0943: */
0944: protected void addWatchedResources(DeployedApplication app,
0945: String docBase, Context context) {
0946: // FIXME: Feature idea. Add support for patterns (ex: WEB-INF/*, WEB-INF/*.xml), where
0947: // we would only check if at least one resource is newer than app.timestamp
0948: File docBaseFile = null;
0949: if (docBase != null) {
0950: docBaseFile = new File(docBase);
0951: if (!docBaseFile.isAbsolute()) {
0952: docBaseFile = new File(appBase(), docBase);
0953: }
0954: }
0955: String[] watchedResources = context.findWatchedResources();
0956: for (int i = 0; i < watchedResources.length; i++) {
0957: File resource = new File(watchedResources[i]);
0958: if (!resource.isAbsolute()) {
0959: if (docBase != null) {
0960: resource = new File(docBaseFile,
0961: watchedResources[i]);
0962: } else {
0963: continue;
0964: }
0965: }
0966: app.reloadResources.put(resource.getAbsolutePath(),
0967: new Long(resource.lastModified()));
0968: }
0969: }
0970:
0971: /**
0972: * Check resources for redeployment and reloading.
0973: */
0974: protected synchronized void checkResources(DeployedApplication app) {
0975: String[] resources = (String[]) app.redeployResources.keySet()
0976: .toArray(new String[0]);
0977: for (int i = 0; i < resources.length; i++) {
0978: File resource = new File(resources[i]);
0979: if (log.isDebugEnabled())
0980: log.debug("Checking context[" + app.name
0981: + "] redeploy resource " + resource);
0982: if (resource.exists()) {
0983: long lastModified = ((Long) app.redeployResources
0984: .get(resources[i])).longValue();
0985: if ((!resource.isDirectory())
0986: && resource.lastModified() > lastModified) {
0987: // Undeploy application
0988: if (log.isInfoEnabled())
0989: log.info(sm.getString("hostConfig.undeploy",
0990: app.name));
0991: ContainerBase context = (ContainerBase) host
0992: .findChild(app.name);
0993: try {
0994: host.removeChild(context);
0995: } catch (Throwable t) {
0996: log.warn(sm.getString(
0997: "hostConfig.context.remove", app.name),
0998: t);
0999: }
1000: try {
1001: context.destroy();
1002: } catch (Throwable t) {
1003: log.warn(
1004: sm.getString(
1005: "hostConfig.context.destroy",
1006: app.name), t);
1007: }
1008: // Delete other redeploy resources
1009: for (int j = i + 1; j < resources.length; j++) {
1010: try {
1011: File current = new File(resources[j]);
1012: current = current.getCanonicalFile();
1013: if ((current.getAbsolutePath()
1014: .startsWith(appBase()
1015: .getAbsolutePath()))
1016: || (current.getAbsolutePath()
1017: .startsWith(configBase()
1018: .getAbsolutePath()))) {
1019: if (log.isDebugEnabled())
1020: log.debug("Delete " + current);
1021: ExpandWar.delete(current);
1022: }
1023: } catch (IOException e) {
1024: log.warn(sm.getString(
1025: "hostConfig.canonicalizing",
1026: app.name), e);
1027: }
1028: }
1029: deployed.remove(app.name);
1030: return;
1031: }
1032: } else {
1033: long lastModified = ((Long) app.redeployResources
1034: .get(resources[i])).longValue();
1035: if (lastModified == 0L) {
1036: continue;
1037: }
1038: // Undeploy application
1039: if (log.isInfoEnabled())
1040: log.info(sm.getString("hostConfig.undeploy",
1041: app.name));
1042: ContainerBase context = (ContainerBase) host
1043: .findChild(app.name);
1044: try {
1045: host.removeChild(context);
1046: } catch (Throwable t) {
1047: log.warn(sm.getString("hostConfig.context.remove",
1048: app.name), t);
1049: }
1050: try {
1051: context.destroy();
1052: } catch (Throwable t) {
1053: log.warn(sm.getString("hostConfig.context.destroy",
1054: app.name), t);
1055: }
1056: // Delete all redeploy resources
1057: for (int j = i + 1; j < resources.length; j++) {
1058: try {
1059: File current = new File(resources[j]);
1060: current = current.getCanonicalFile();
1061: if ((current.getAbsolutePath()
1062: .startsWith(appBase().getAbsolutePath()))
1063: || (current.getAbsolutePath()
1064: .startsWith(configBase()
1065: .getAbsolutePath()))) {
1066: if (log.isDebugEnabled())
1067: log.debug("Delete " + current);
1068: ExpandWar.delete(current);
1069: }
1070: } catch (IOException e) {
1071: log.warn(sm.getString(
1072: "hostConfig.canonicalizing", app.name),
1073: e);
1074: }
1075: }
1076: // Delete reload resources as well (to remove any remaining .xml descriptor)
1077: String[] resources2 = (String[]) app.reloadResources
1078: .keySet().toArray(new String[0]);
1079: for (int j = 0; j < resources2.length; j++) {
1080: try {
1081: File current = new File(resources2[j]);
1082: current = current.getCanonicalFile();
1083: if ((current.getAbsolutePath()
1084: .startsWith(appBase().getAbsolutePath()))
1085: || ((current
1086: .getAbsolutePath()
1087: .startsWith(
1088: configBase()
1089: .getAbsolutePath()) && (current
1090: .getAbsolutePath()
1091: .endsWith(".xml"))))) {
1092: if (log.isDebugEnabled())
1093: log.debug("Delete " + current);
1094: ExpandWar.delete(current);
1095: }
1096: } catch (IOException e) {
1097: log.warn(sm.getString(
1098: "hostConfig.canonicalizing", app.name),
1099: e);
1100: }
1101: }
1102: deployed.remove(app.name);
1103: return;
1104: }
1105: }
1106: resources = (String[]) app.reloadResources.keySet().toArray(
1107: new String[0]);
1108: for (int i = 0; i < resources.length; i++) {
1109: File resource = new File(resources[i]);
1110: if (log.isDebugEnabled())
1111: log.debug("Checking context[" + app.name
1112: + "] reload resource " + resource);
1113: long lastModified = ((Long) app.reloadResources
1114: .get(resources[i])).longValue();
1115: if ((!resource.exists() && lastModified != 0L)
1116: || (resource.lastModified() != lastModified)) {
1117: // Reload application
1118: if (log.isInfoEnabled())
1119: log.info(sm
1120: .getString("hostConfig.reload", app.name));
1121: Container context = host.findChild(app.name);
1122: try {
1123: ((Lifecycle) context).stop();
1124: } catch (Exception e) {
1125: log.warn(sm.getString("hostConfig.context.restart",
1126: app.name), e);
1127: }
1128: // If the context was not started (for example an error
1129: // in web.xml) we'll still get to try to start
1130: try {
1131: ((Lifecycle) context).start();
1132: } catch (Exception e) {
1133: log.warn(sm.getString("hostConfig.context.restart",
1134: app.name), e);
1135: }
1136: // Update times
1137: app.reloadResources.put(resources[i], new Long(resource
1138: .lastModified()));
1139: app.timestamp = System.currentTimeMillis();
1140: return;
1141: }
1142: }
1143: }
1144:
1145: /**
1146: * Process a "start" event for this Host.
1147: */
1148: public void start() {
1149:
1150: if (log.isDebugEnabled())
1151: log.debug(sm.getString("hostConfig.start"));
1152:
1153: try {
1154: ObjectName hostON = new ObjectName(host.getObjectName());
1155: oname = new ObjectName(hostON.getDomain()
1156: + ":type=Deployer,host=" + host.getName());
1157: Registry.getRegistry(null, null).registerComponent(this ,
1158: oname, this .getClass().getName());
1159: } catch (Exception e) {
1160: log
1161: .error(sm.getString("hostConfig.jmx.register",
1162: oname), e);
1163: }
1164:
1165: if (host.getDeployOnStartup())
1166: deployApps();
1167:
1168: }
1169:
1170: /**
1171: * Process a "stop" event for this Host.
1172: */
1173: public void stop() {
1174:
1175: if (log.isDebugEnabled())
1176: log.debug(sm.getString("hostConfig.stop"));
1177:
1178: undeployApps();
1179:
1180: if (oname != null) {
1181: try {
1182: Registry.getRegistry(null, null).unregisterComponent(
1183: oname);
1184: } catch (Exception e) {
1185: log.error(sm.getString("hostConfig.jmx.unregister",
1186: oname), e);
1187: }
1188: }
1189: oname = null;
1190: appBase = null;
1191: configBase = null;
1192:
1193: }
1194:
1195: /**
1196: * Undeploy all deployed applications.
1197: */
1198: protected void undeployApps() {
1199:
1200: if (log.isDebugEnabled())
1201: log.debug(sm.getString("hostConfig.undeploying"));
1202:
1203: // Soft undeploy all contexts we have deployed
1204: DeployedApplication[] apps = (DeployedApplication[]) deployed
1205: .values().toArray(new DeployedApplication[0]);
1206: for (int i = 0; i < apps.length; i++) {
1207: try {
1208: host.removeChild(host.findChild(apps[i].name));
1209: } catch (Throwable t) {
1210: log.warn(sm.getString("hostConfig.context.remove",
1211: apps[i].name), t);
1212: }
1213: }
1214:
1215: deployed.clear();
1216:
1217: }
1218:
1219: /**
1220: * Check status of all webapps.
1221: */
1222: protected void check() {
1223:
1224: if (host.getAutoDeploy()) {
1225: // Check for resources modification to trigger redeployment
1226: DeployedApplication[] apps = (DeployedApplication[]) deployed
1227: .values().toArray(new DeployedApplication[0]);
1228: for (int i = 0; i < apps.length; i++) {
1229: if (!isServiced(apps[i].name))
1230: checkResources(apps[i]);
1231: }
1232: // Hotdeploy applications
1233: deployApps();
1234: }
1235:
1236: }
1237:
1238: /**
1239: * Check status of a specific webapp, for use with stuff like management webapps.
1240: */
1241: public void check(String name) {
1242: DeployedApplication app = (DeployedApplication) deployed
1243: .get(name);
1244: if (app != null) {
1245: checkResources(app);
1246: } else {
1247: deployApps(name);
1248: }
1249: }
1250:
1251: /**
1252: * Add a new Context to be managed by us.
1253: * Entry point for the admin webapp, and other JMX Context controlers.
1254: */
1255: public void manageApp(Context context) {
1256:
1257: String contextPath = context.getPath();
1258:
1259: if (deployed.containsKey(contextPath))
1260: return;
1261:
1262: DeployedApplication deployedApp = new DeployedApplication(
1263: contextPath);
1264:
1265: // Add the associated docBase to the redeployed list if it's a WAR
1266: boolean isWar = false;
1267: if (context.getDocBase() != null) {
1268: File docBase = new File(context.getDocBase());
1269: if (!docBase.isAbsolute()) {
1270: docBase = new File(appBase(), context.getDocBase());
1271: }
1272: deployedApp.redeployResources.put(
1273: docBase.getAbsolutePath(), new Long(docBase
1274: .lastModified()));
1275: if (docBase.getAbsolutePath().toLowerCase()
1276: .endsWith(".war")) {
1277: isWar = true;
1278: }
1279: }
1280: host.addChild(context);
1281: // Add the eventual unpacked WAR and all the resources which will be
1282: // watched inside it
1283: if (isWar && unpackWARs) {
1284: String name = null;
1285: String path = context.getPath();
1286: if (path.equals("")) {
1287: name = "ROOT";
1288: } else {
1289: if (path.startsWith("/")) {
1290: name = path.substring(1);
1291: } else {
1292: name = path;
1293: }
1294: }
1295: File docBase = new File(name);
1296: if (!docBase.isAbsolute()) {
1297: docBase = new File(appBase(), name);
1298: }
1299: deployedApp.redeployResources.put(
1300: docBase.getAbsolutePath(), new Long(docBase
1301: .lastModified()));
1302: addWatchedResources(deployedApp, docBase.getAbsolutePath(),
1303: context);
1304: } else {
1305: addWatchedResources(deployedApp, null, context);
1306: }
1307: deployed.put(contextPath, deployedApp);
1308: }
1309:
1310: /**
1311: * Remove a webapp from our control.
1312: * Entry point for the admin webapp, and other JMX Context controlers.
1313: */
1314: public void unmanageApp(String contextPath) {
1315: if (isServiced(contextPath)) {
1316: deployed.remove(contextPath);
1317: host.removeChild(host.findChild(contextPath));
1318: }
1319: }
1320:
1321: // ----------------------------------------------------- Instance Variables
1322:
1323: /**
1324: * This class represents the state of a deployed application, as well as
1325: * the monitored resources.
1326: */
1327: protected class DeployedApplication {
1328: public DeployedApplication(String name) {
1329: this .name = name;
1330: }
1331:
1332: /**
1333: * Application context path. The assertion is that
1334: * (host.getChild(name) != null).
1335: */
1336: public String name;
1337:
1338: /**
1339: * Any modification of the specified (static) resources will cause a
1340: * redeployment of the application. If any of the specified resources is
1341: * removed, the application will be undeployed. Typically, this will
1342: * contain resources like the context.xml file, a compressed WAR path.
1343: * The value is the last modification time.
1344: */
1345: public LinkedHashMap redeployResources = new LinkedHashMap();
1346:
1347: /**
1348: * Any modification of the specified (static) resources will cause a
1349: * reload of the application. This will typically contain resources
1350: * such as the web.xml of a webapp, but can be configured to contain
1351: * additional descriptors.
1352: * The value is the last modification time.
1353: */
1354: public HashMap reloadResources = new HashMap();
1355:
1356: /**
1357: * Instant where the application was last put in service.
1358: */
1359: public long timestamp = System.currentTimeMillis();
1360: }
1361:
1362: }
|