/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.catalina.startup;
/** * Startup event listener for a <b>Host</b> that configures the properties * of that Host, and the associated defined contexts. * * @author Craig R. McClanahan * @author Remy Maucherat
*/ publicclass HostConfig implements LifecycleListener {
/** * The Java class name of the Context implementation we should use.
*/ protected String contextClass = "org.apache.catalina.core.StandardContext";
/** * The Host we are associated with.
*/ protected Host host = null;
/** * The JMX ObjectName of this component.
*/ protected ObjectName oname = null;
/** * Should we deploy XML Context config files packaged with WAR files and * directories?
*/ protectedboolean deployXML = false;
/** * Should XML files be copied to * $CATALINA_BASE/conf/<engine>/<host> by default when * a web application is deployed?
*/ protectedboolean copyXML = false;
/** * Should we unpack WAR files when auto-deploying applications in the * <code>appBase</code> directory?
*/ protectedboolean unpackWARs = false;
/** * Map of deployed applications.
*/ protectedfinal Map<String, DeployedApplication> deployed = new ConcurrentHashMap<>();
/** * Set of applications which are being serviced, and shouldn't be * deployed/undeployed/redeployed at the moment.
*/ private Set<String> servicedSet = ConcurrentHashMap.newKeySet();
/** * The <code>Digester</code> instance used to parse context descriptors.
*/ protected Digester digester = createDigester(contextClass); privatefinal Object digesterLock = new Object();
/** * The list of Wars in the appBase to be ignored because they are invalid * (e.g. contain /../ sequences).
*/ protectedfinal Set<String> invalidWars = new HashSet<>();
/** * @return the Context implementation class name.
*/ public String getContextClass() { returnthis.contextClass;
}
/** * Set the Context implementation class name. * * @param contextClass The new Context implementation class name.
*/ publicvoid setContextClass(String contextClass) {
/** * @return the deploy XML config file flag for this component.
*/ publicboolean isDeployXML() { returnthis.deployXML;
}
/** * Set the deploy XML config file flag for this component. * * @param deployXML The new deploy XML flag
*/ publicvoid setDeployXML(boolean deployXML) { this.deployXML = deployXML;
}
privateboolean isDeployThisXML(File docBase, ContextName cn) { boolean deployThisXML = isDeployXML(); if (Globals.IS_SECURITY_ENABLED && !deployThisXML) { // When running under a SecurityManager, deployXML may be overridden // on a per Context basis by the granting of a specific permission
Policy currentPolicy = Policy.getPolicy(); if (currentPolicy != null) {
URL contextRootUrl; try {
contextRootUrl = docBase.toURI().toURL();
CodeSource cs = new CodeSource(contextRootUrl, (Certificate[]) null);
PermissionCollection pc = currentPolicy.getPermissions(cs);
Permission p = new DeployXmlPermission(cn.getBaseName()); if (pc.implies(p)) {
deployThisXML = true;
}
} catch (MalformedURLException e) { // Should never happen
log.warn(sm.getString("hostConfig.docBaseUrlInvalid"), e);
}
}
}
return deployThisXML;
}
/** * @return the copy XML config file flag for this component.
*/ publicboolean isCopyXML() { returnthis.copyXML;
}
/** * Set the copy XML config file flag for this component. * * @param copyXML The new copy XML flag
*/ publicvoid setCopyXML(boolean copyXML) {
/** * Set the unpack WARs flag. * * @param unpackWARs The new unpack WARs flag
*/ publicvoid setUnpackWARs(boolean unpackWARs) { this.unpackWARs = unpackWARs;
}
// --------------------------------------------------------- Public Methods
/** * Process the START event for an associated Host. * * @param event The lifecycle event that has occurred
*/
@Override publicvoid lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with try {
host = (Host) event.getLifecycle(); if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e); return;
}
// Process the event that has occurred if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} elseif (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} elseif (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} elseif (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
/** * Add a serviced application to the list and indicates if the application * was already present in the list. * * @param name the context name * * @return {@code true} if the application was not already in the list
*/ publicboolean tryAddServiced(String name) { if (servicedSet.add(name)) { returntrue;
} returnfalse;
}
/** * Removed a serviced application from the list. * @param name the context name
*/ publicvoid removeServiced(String name) {
servicedSet.remove(name);
}
/** * Get the instant where an application was deployed. * @param name the context name * @return 0L if no application with that name is deployed, or the instant * on which the application was deployed
*/ publicsynchronizedlong getDeploymentTime(String name) {
DeployedApplication app = deployed.get(name); if (app == null) { return 0L;
}
return app.timestamp;
}
/** * Has the specified application been deployed? Note applications defined * in server.xml will not have been deployed. * @param name the context name * @return <code>true</code> if the application has been deployed and * <code>false</code> if the application has not been deployed or does not * exist
*/ publicboolean isDeployed(String name) { return deployed.containsKey(name);
}
/** * Create the digester which will be used to parse context config files. * @param contextClassName The class which will be used to create the * context instance * @return the digester
*/ protectedstatic Digester createDigester(String contextClassName) {
Digester digester = new Digester();
digester.setValidating(false); // Add object creation rule
digester.addObjectCreate("Context", contextClassName, "className"); // Set the properties on that object (it doesn't matter if extra // properties are set)
digester.addSetProperties("Context"); return digester;
}
/** * Get the name of the configBase. * For use with JMX management. * @return the config base
*/ public String getConfigBaseName() { return host.getConfigBaseFile().getAbsolutePath();
}
/** * Deploy applications for any directories or WAR files that are found * in our "application root" directory.
*/ protectedvoid deployApps() { // Migrate legacy Java EE apps from legacyAppBase
migrateLegacyApps();
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list()); // Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list()); // Deploy WARs
deployWARs(appBase, filteredAppPaths); // Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
/** * Filter the list of application file paths to remove those that match * the regular expression defined by {@link Host#getDeployIgnore()}. * * @param unfilteredAppPaths The list of application paths to filter * * @return The filtered list of application paths
*/ protected String[] filterAppPaths(String[] unfilteredAppPaths) {
Pattern filter = host.getDeployIgnorePattern(); if (filter == null || unfilteredAppPaths == null) { return unfilteredAppPaths;
}
List<String> filteredList = new ArrayList<>();
Matcher matcher = null; for (String appPath : unfilteredAppPaths) { if (matcher == null) {
matcher = filter.matcher(appPath);
} else {
matcher.reset(appPath);
} if (matcher.matches()) { if (log.isDebugEnabled()) {
log.debug(sm.getString("hostConfig.ignorePath", appPath));
}
} else {
filteredList.add(appPath);
}
} return filteredList.toArray(new String[0]);
}
/** * Deploy applications for any directories or WAR files that are found * in our "application root" directory. * <p> * Note: It is expected that the caller has successfully added the app * to servicedSet before calling this method. * * @param name The context name which should be deployed
*/ protectedvoid deployApps(String name) {
// Deploy XML descriptor from configBase
File xml = new File(configBase, baseName + ".xml"); if (xml.exists()) {
deployDescriptor(cn, xml); return;
} // Deploy WAR
File war = new File(appBase, baseName + ".war"); if (war.exists()) {
deployWAR(cn, war); return;
} // Deploy expanded folder
File dir = new File(appBase, baseName); if (dir.exists()) {
deployDirectory(cn, dir);
}
}
/** * Deploy XML context descriptors. * @param configBase The config base * @param files The XML descriptors which should be deployed
*/ protectedvoid deployDescriptors(File configBase, String[] files) {
if (files == null) { return;
}
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (String file : files) {
File contextXml = new File(configBase, file);
if (file.toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
ContextName cn = new ContextName(file, true);
if (tryAddServiced(cn.getName())) { try { if (deploymentExists(cn.getName())) {
removeServiced(cn.getName()); continue;
}
for (Future<?> result : results) { try {
result.get();
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployDescriptor.threaded.error"), e);
}
}
}
/** * Deploy specified context descriptor. * <p> * Note: It is expected that the caller has successfully added the app * to servicedSet before calling this method. * * @param cn The context name * @param contextXml The descriptor
*/
@SuppressWarnings("null") // context is not null protectedvoid deployDescriptor(ContextName cn, File contextXml) {
DeployedApplication deployedApp = new DeployedApplication(cn.getName(), true);
long startTime = 0; // Assume this is a configuration descriptor and deploy it if (log.isInfoEnabled()) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployDescriptor", contextXml.getAbsolutePath()));
}
context.setConfigFile(contextXml.toURI().toURL());
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion()); // Add the associated docBase to the redeployed list if it's a WAR if (context.getDocBase() != null) {
File docBase = new File(context.getDocBase()); if (!docBase.isAbsolute()) {
docBase = new File(host.getAppBaseFile(), context.getDocBase());
} // If external docBase, register .xml as redeploy first if (!docBase.getCanonicalFile().toPath().startsWith(host.getAppBaseFile().toPath())) {
isExternal = true;
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(), Long.valueOf(contextXml.lastModified()));
deployedApp.redeployResources.put(
docBase.getAbsolutePath(), Long.valueOf(docBase.lastModified())); if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
isExternalWar = true;
} // Check that a WAR or DIR in the appBase is not 'hidden'
File war = new File(host.getAppBaseFile(), cn.getBaseName() + ".war"); if (war.exists()) {
log.warn(sm.getString("hostConfig.deployDescriptor.hiddenWar",
contextXml.getAbsolutePath(), war.getAbsolutePath()));
}
File dir = new File(host.getAppBaseFile(), cn.getBaseName()); if (dir.exists()) {
log.warn(sm.getString("hostConfig.deployDescriptor.hiddenDir",
contextXml.getAbsolutePath(), dir.getAbsolutePath()));
}
} else {
log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified", docBase)); // Ignore specified docBase
context.setDocBase(null);
}
}
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDescriptor.error", contextXml.getAbsolutePath()), t);
} finally { // Get paths for WAR and expanded WAR in appBase
// default to appBase dir + name
expandedDocBase = new File(host.getAppBaseFile(), cn.getBaseName()); if (context.getDocBase() != null && !context.getDocBase().toLowerCase(Locale.ENGLISH).endsWith(".war")) { // first assume docBase is absolute
expandedDocBase = new File(context.getDocBase()); if (!expandedDocBase.isAbsolute()) { // if docBase specified and relative, it must be relative to appBase
expandedDocBase = new File(host.getAppBaseFile(), context.getDocBase());
}
}
// Add the eventual unpacked WAR and all the resources which will be // watched inside it if (isExternalWar) { if (unpackWAR) {
deployedApp.redeployResources.put(
expandedDocBase.getAbsolutePath(), Long.valueOf(expandedDocBase.lastModified()));
addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
} else {
addWatchedResources(deployedApp, null, context);
}
} else { // Find an existing matching war and expanded folder if (!isExternal) {
File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war"); if (warDocBase.exists()) {
deployedApp.redeployResources.put(
warDocBase.getAbsolutePath(), Long.valueOf(warDocBase.lastModified()));
} else { // Trigger a redeploy if a WAR is added
deployedApp.redeployResources.put(warDocBase.getAbsolutePath(), Long.valueOf(0));
}
} if (unpackWAR) {
deployedApp.redeployResources.put(
expandedDocBase.getAbsolutePath(), Long.valueOf(expandedDocBase.lastModified()));
addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
} else {
addWatchedResources(deployedApp, null, context);
} if (!isExternal) { // For external docBases, the context.xml will have been // added above.
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(), Long.valueOf(contextXml.lastModified()));
}
} // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
if (host.findChild(context.getName()) != null) {
deployed.put(context.getName(), deployedApp);
}
if (log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.deployDescriptor.finished",
contextXml.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
/** * Deploy WAR files. * @param appBase The base path for applications * @param files The WARs to deploy
*/ protectedvoid deployWARs(File appBase, String[] files) {
if (files == null) { return;
}
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (String file : files) { if (file.equalsIgnoreCase("META-INF")) { continue;
} if (file.equalsIgnoreCase("WEB-INF")) { continue;
}
File war = new File(appBase, file); if (file.toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && !invalidWars.contains(file)) {
ContextName cn = new ContextName(file, true); if (tryAddServiced(cn.getName())) { try { if (deploymentExists(cn.getName())) {
DeployedApplication app = deployed.get(cn.getName()); boolean unpackWAR = unpackWARs; if (unpackWAR && host.findChild(cn.getName()) instanceof StandardContext) {
unpackWAR = ((StandardContext) host.findChild(cn.getName())).getUnpackWAR();
} if (!unpackWAR && app != null) { // Need to check for a directory that should not be // there
File dir = new File(appBase, cn.getBaseName()); if (dir.exists()) { if (!app.loggedDirWarning) {
log.warn(sm.getString("hostConfig.deployWar.hiddenDir",
dir.getAbsoluteFile(), war.getAbsoluteFile()));
app.loggedDirWarning = true;
}
} else {
app.loggedDirWarning = false;
}
}
removeServiced(cn.getName()); continue;
}
// Check for WARs with /../ /./ or similar sequences in the name if (!validateContextPath(appBase, cn.getBaseName())) {
log.error(sm.getString("hostConfig.illegalWarName", file));
invalidWars.add(file);
removeServiced(cn.getName()); continue;
}
for (Future<?> result : results) { try {
result.get();
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployWar.threaded.error"), e);
}
}
}
privateboolean validateContextPath(File appBase, String contextPath) { // More complicated than the ideal as the canonical path may or may // not end with File.separator for a directory
try {
String canonicalAppBase = appBase.getCanonicalPath();
docBase = new StringBuilder(canonicalAppBase); if (canonicalAppBase.endsWith(File.separator)) {
docBase.append(contextPath.substring(1).replace('/', File.separatorChar));
} else {
docBase.append(contextPath.replace('/', File.separatorChar));
} // At this point docBase should be canonical but will not end // with File.separator
canonicalDocBase = (new File(docBase.toString())).getCanonicalPath();
// If the canonicalDocBase ends with File.separator, add one to // docBase before they are compared if (canonicalDocBase.endsWith(File.separator)) {
docBase.append(File.separator);
}
} catch (IOException ioe) { returnfalse;
}
// Compare the two. If they are not the same, the contextPath must // have /../ like sequences in it return canonicalDocBase.equals(docBase.toString());
}
/** * Deploy packed WAR. * <p> * Note: It is expected that the caller has successfully added the app * to servicedSet before calling this method. * * @param cn The context name * @param war The WAR file
*/ protectedvoid deployWAR(ContextName cn, File war) {
File xml = new File(host.getAppBaseFile(), cn.getBaseName() + "/" + Constants.ApplicationContextXml);
File warTracker = new File(host.getAppBaseFile(), cn.getBaseName() + Constants.WarTracker);
// If there is an expanded directory then any xml in that directory // should only be used if the directory is not out of date and // unpackWARs is true. Note the code below may apply further limits boolean useXml = false; // If the xml file exists then expandedDir must exists so no need to // test that here if (xml.exists() && unpackWARs && (!warTracker.exists() || warTracker.lastModified() == war.lastModified())) {
useXml = true;
}
boolean copyThisXml = false; if (deployThisXML) { if (host instanceof StandardHost) {
copyThisXml = ((StandardHost) host).isCopyXML();
}
// If Host is using default value Context can override it. if (!copyThisXml && context instanceof StandardContext) {
copyThisXml = ((StandardContext) context).getCopyXML();
}
if (xmlInWar && copyThisXml) { // Change location of XML file to config base
xml = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"); try (JarFile jar = new JarFile(war)) {
JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml); try (InputStream istream = jar.getInputStream(entry);
OutputStream ostream = new FileOutputStream(xml)) {
IOTools.flow(istream, ostream);
}
} catch (IOException e) { /* Ignore */
}
}
}
long startTime = 0; // Deploy the application in this WAR file if(log.isInfoEnabled()) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployWar", war.getAbsolutePath()));
}
try { // Populate redeploy resources with the WAR file
deployedApp.redeployResources.put(war.getAbsolutePath(), Long.valueOf(war.lastModified()));
if (deployThisXML && xml.exists() && copyThisXml) {
deployedApp.redeployResources.put(xml.getAbsolutePath(), Long.valueOf(xml.lastModified()));
} else { // In case an XML file is added to the config base later
deployedApp.redeployResources.put(
(new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml")).getAbsolutePath(), Long.valueOf(0));
}
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName() + ".war");
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployWar.error", war.getAbsolutePath()), t);
} finally { // If we're unpacking WARs, the docBase will be mutated after // starting the context boolean unpackWAR = unpackWARs; if (unpackWAR && context instanceof StandardContext) {
unpackWAR = ((StandardContext) context).getUnpackWAR();
} if (unpackWAR && context.getDocBase() != null) {
File docBase = new File(host.getAppBaseFile(), cn.getBaseName());
deployedApp.redeployResources.put(docBase.getAbsolutePath(), Long.valueOf(docBase.lastModified()));
addWatchedResources(deployedApp, docBase.getAbsolutePath(), context); if (deployThisXML && !copyThisXml && (xmlInWar || xml.exists())) {
deployedApp.redeployResources.put(xml.getAbsolutePath(), Long.valueOf(xml.lastModified()));
}
} else { // Passing null for docBase means that no resources will be // watched. This will be logged at debug level.
addWatchedResources(deployedApp, null, context);
} // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
deployed.put(cn.getName(), deployedApp);
if (log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.deployWar.finished",
war.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
/** * Deploy exploded webapps. * @param appBase The base path for applications * @param files The exploded webapps that should be deployed
*/ protectedvoid deployDirectories(File appBase, String[] files) {
if (files == null) { return;
}
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (String file : files) { if (file.equalsIgnoreCase("META-INF")) { continue;
} if (file.equalsIgnoreCase("WEB-INF")) { continue;
}
File dir = new File(appBase, file); if (dir.isDirectory()) {
ContextName cn = new ContextName(file, false);
if (tryAddServiced(cn.getName())) { try { if (deploymentExists(cn.getName())) {
removeServiced(cn.getName()); continue;
}
for (Future<?> result : results) { try {
result.get();
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployDir.threaded.error"), e);
}
}
}
/** * Deploy exploded webapp. * <p> * Note: It is expected that the caller has successfully added the app * to servicedSet before calling this method. * * @param cn The context name * @param dir The path to the root folder of the webapp
*/ protectedvoid deployDirectory(ContextName cn, File dir) {
long startTime = 0; // Deploy the application in this directory if( log.isInfoEnabled() ) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployDir", dir.getAbsolutePath()));
}
Context context = null;
File xml = new File(dir, Constants.ApplicationContextXml);
File xmlCopy = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
if (copyThisXml == false && context instanceof StandardContext) { // Host is using default value. Context may override it.
copyThisXml = ((StandardContext) context).getCopyXML();
}
if (copyThisXml) {
Files.copy(xml.toPath(), xmlCopy.toPath());
context.setConfigFile(xmlCopy.toURI().toURL());
} else {
context.setConfigFile(xml.toURI().toURL());
}
} elseif (!deployThisXML && xml.exists()) { // Block deployment as META-INF/context.xml may contain security // configuration necessary for a secure deployment.
log.error(sm.getString("hostConfig.deployDescriptor.blocked", cn.getPath(), xml, xmlCopy));
context = new FailedContext();
} else {
context = (Context) Class.forName(contextClass).getConstructor().newInstance();
}
// Fake re-deploy resource to detect if a WAR is added at a later // point
deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war", Long.valueOf(0));
deployedApp.redeployResources.put(dir.getAbsolutePath(), Long.valueOf(dir.lastModified())); if (deployThisXML && xml.exists()) { if (copyThisXml) {
deployedApp.redeployResources.put(xmlCopy.getAbsolutePath(), Long.valueOf(xmlCopy.lastModified()));
} else {
deployedApp.redeployResources.put(xml.getAbsolutePath(), Long.valueOf(xml.lastModified())); // Fake re-deploy resource to detect if a context.xml file is // added at a later point
deployedApp.redeployResources.put(xmlCopy.getAbsolutePath(), Long.valueOf(0));
}
} else { // Fake re-deploy resource to detect if a context.xml file is // added at a later point
deployedApp.redeployResources.put(xmlCopy.getAbsolutePath(), Long.valueOf(0)); if (!xml.exists()) {
deployedApp.redeployResources.put(xml.getAbsolutePath(), Long.valueOf(0));
}
}
addWatchedResources(deployedApp, dir.getAbsolutePath(), context); // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
// Should not be null as we test above if this is a directory
String[] migrationCandidates = legacyAppBase.list(); if (migrationCandidates == null) { return;
} for (String migrationCandidate : migrationCandidates) {
File source = new File(legacyAppBase, migrationCandidate);
File destination = new File(appBase, migrationCandidate);
ContextName cn; if (source.lastModified() > destination.lastModified()) { if (source.isFile() && source.getName().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
cn = new ContextName(migrationCandidate, true);
} elseif (source.isDirectory()) {
cn = new ContextName(migrationCandidate, false);
} else { continue;
}
for (Future<?> result : results) { try {
result.get();
} catch (Exception e) {
log.error(sm.getString("hostConfig.migrateApp.threaded.error"), e);
}
}
}
protectedvoid migrateLegacyApp(File source, File destination) {
File tempNew = null;
File tempOld = null; try {
tempNew = File.createTempFile("new", null, host.getLegacyAppBaseFile());
tempOld = File.createTempFile("old", null, host.getLegacyAppBaseFile()); // createTempFile is not directly compatible with directories, so cleanup
Files.delete(tempNew.toPath());
Files.delete(tempOld.toPath());
// The use of defaults is deliberate here to avoid having to // recreate every configuration option on the host. Better to change // the defaults if necessary than to start adding configuration // options. Users that need non-default options can convert manually // via migration.[sh|bat]
Migration migration = new Migration();
migration.setSource(source);
migration.setDestination(tempNew);
migration.execute();
// Use rename if (destination.exists()) {
Files.move(destination.toPath(), tempOld.toPath());
}
Files.move(tempNew.toPath(), destination.toPath());
ExpandWar.delete(tempOld);
/** * Check if a webapp is already deployed in this host. * * @param contextName of the context which will be checked * @return <code>true</code> if the specified deployment exists
*/ protectedboolean deploymentExists(String contextName) { return deployed.containsKey(contextName) || (host.findChild(contextName) != null);
}
/** * Add watched resources to the specified Context. * @param app HostConfig deployed app * @param docBase web app docBase * @param context web application context
*/ protectedvoid addWatchedResources(DeployedApplication app, String docBase, Context context) { // FIXME: Feature idea. Add support for patterns (ex: WEB-INF/*, // WEB-INF/*.xml), where we would only check if at least one // resource is newer than app.timestamp
File docBaseFile = null; if (docBase != null) {
docBaseFile = new File(docBase); if (!docBaseFile.isAbsolute()) {
docBaseFile = new File(host.getAppBaseFile(), docBase);
}
}
String[] watchedResources = context.findWatchedResources(); for (String watchedResource : watchedResources) {
File resource = new File(watchedResource); if (!resource.isAbsolute()) { if (docBase != null) {
resource = new File(docBaseFile, watchedResource);
} else { if (log.isDebugEnabled()) {
log.debug("Ignoring non-existent WatchedResource '" +
resource.getAbsolutePath() + "'");
} continue;
}
} if (log.isDebugEnabled()) {
log.debug("Watching WatchedResource '" +
resource.getAbsolutePath() + "'");
}
app.reloadResources.put(resource.getAbsolutePath(), Long.valueOf(resource.lastModified()));
}
}
protectedvoid addGlobalRedeployResources(DeployedApplication app) { // Redeploy resources processing is hard-coded to never delete this file
File hostContextXml = new File(getConfigBaseName(), Constants.HostContextXml); if (hostContextXml.isFile()) {
app.redeployResources.put(hostContextXml.getAbsolutePath(), Long.valueOf(hostContextXml.lastModified()));
}
// Redeploy resources in CATALINA_BASE/conf are never deleted
File globalContextXml =
returnCanonicalPath(Constants.DefaultContextXml); if (globalContextXml.isFile()) {
app.redeployResources.put(globalContextXml.getAbsolutePath(), Long.valueOf(globalContextXml.lastModified()));
}
}
/** * Check resources for redeployment and reloading. * * @param app The web application to check * @param skipFileModificationResolutionCheck * When checking files for modification should the check that * requires that any file modification must have occurred at * least as long ago as the resolution of the file time stamp * be skipped
*/ protectedsynchronizedvoid checkResources(DeployedApplication app, boolean skipFileModificationResolutionCheck) {
String[] resources =
app.redeployResources.keySet().toArray(new String[0]); // Offset the current time by the resolution of File.lastModified() long currentTimeWithResolutionOffset =
System.currentTimeMillis() - FILE_MODIFICATION_RESOLUTION_MS; for (int i = 0; i < resources.length; i++) {
File resource = new File(resources[i]); if (log.isDebugEnabled()) {
log.debug("Checking context[" + app.name + "] redeploy resource " + resource);
} long lastModified =
app.redeployResources.get(resources[i]).longValue(); if (resource.exists() || lastModified == 0) { // File.lastModified() has a resolution of 1s (1000ms). The last // modified time has to be more than 1000ms ago to ensure that // modifications that take place in the same second are not // missed. See Bug 57765. if (resource.lastModified() != lastModified && (!host.getAutoDeploy() ||
resource.lastModified() < currentTimeWithResolutionOffset ||
skipFileModificationResolutionCheck)) { if (resource.isDirectory()) { // No action required for modified directory
app.redeployResources.put(resources[i], Long.valueOf(resource.lastModified()));
} elseif (app.hasDescriptor &&
resource.getName().toLowerCase(
Locale.ENGLISH).endsWith(".war")) { // Modified WAR triggers a reload if there is an XML // file present // The only resource that should be deleted is the // expanded WAR (if any)
Context context = (Context) host.findChild(app.name);
String docBase = context.getDocBase(); if (!docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) { // This is an expanded directory
File docBaseFile = new File(docBase); if (!docBaseFile.isAbsolute()) {
docBaseFile = new File(host.getAppBaseFile(),
docBase);
}
reload(app, docBaseFile, resource.getAbsolutePath());
} else {
reload(app, null, null);
} // Update times
app.redeployResources.put(resources[i], Long.valueOf(resource.lastModified()));
app.timestamp = System.currentTimeMillis(); boolean unpackWAR = unpackWARs; if (unpackWAR && context instanceof StandardContext) {
unpackWAR = ((StandardContext) context).getUnpackWAR();
} if (unpackWAR) {
addWatchedResources(app, context.getDocBase(), context);
} else {
addWatchedResources(app, null, context);
} return;
} else { // Everything else triggers a redeploy // (just need to undeploy here, deploy will follow)
undeploy(app);
deleteRedeployResources(app, resources, i, false); return;
}
}
} else { // There is a chance the the resource was only missing // temporarily eg renamed during a text editor save try { Thread.sleep(500);
} catch (InterruptedException e1) { // Ignore
} // Recheck the resource to see if it was really deleted if (resource.exists()) { continue;
} // Undeploy application
undeploy(app);
deleteRedeployResources(app, resources, i, true); return;
}
}
resources = app.reloadResources.keySet().toArray(new String[0]); boolean update = false; for (String s : resources) {
File resource = new File(s); if (log.isDebugEnabled()) {
log.debug("Checking context[" + app.name + "] reload resource " + resource);
} long lastModified = app.reloadResources.get(s).longValue(); // File.lastModified() has a resolution of 1s (1000ms). The last // modified time has to be more than 1000ms ago to ensure that // modifications that take place in the same second are not // missed. See Bug 57765. if ((resource.lastModified() != lastModified &&
(!host.getAutoDeploy() ||
resource.lastModified() < currentTimeWithResolutionOffset ||
skipFileModificationResolutionCheck)) ||
update) { if (!update) { // Reload application
reload(app, null, null);
update = true;
} // Update times. More than one file may have been updated. We // don't want to trigger a series of reloads.
app.reloadResources.put(s, Long.valueOf(resource.lastModified()));
}
app.timestamp = System.currentTimeMillis();
}
}
/* * Note: If either of fileToRemove and newDocBase are null, both will be * ignored.
*/ privatevoid reload(DeployedApplication app, File fileToRemove, String newDocBase) { if(log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.reload", app.name));
}
Context context = (Context) host.findChild(app.name); if (context.getState().isAvailable()) { if (fileToRemove != null && newDocBase != null) {
context.addLifecycleListener( new ExpandedDirectoryRemovalListener(fileToRemove, newDocBase));
} // Reload catches and logs exceptions
context.reload();
} else { // If the context was not started (for example an error // in web.xml) we'll still get to try to start if (fileToRemove != null && newDocBase != null) {
ExpandWar.delete(fileToRemove);
context.setDocBase(newDocBase);
} try {
context.start();
} catch (Exception e) {
log.error(sm.getString("hostConfig.context.restart", app.name), e);
}
}
}
privatevoid deleteRedeployResources(DeployedApplication app, String[] resources, int i, boolean deleteReloadResources) {
// Delete other redeploy resources for (int j = i + 1; j < resources.length; j++) {
File current = new File(resources[j]); // Never delete per host context.xml defaults if (Constants.HostContextXml.equals(current.getName())) { continue;
} // Only delete resources in the appBase or the // host's configBase if (isDeletableResource(app, current)) { if (log.isDebugEnabled()) {
log.debug("Delete " + current);
}
ExpandWar.delete(current);
}
}
// Delete reload resources (to remove any remaining .xml descriptor) if (deleteReloadResources) {
String[] resources2 = app.reloadResources.keySet().toArray(new String[0]); for (String s : resources2) {
File current = new File(s); // Never delete per host context.xml defaults if (Constants.HostContextXml.equals(current.getName())) { continue;
} // Only delete resources in the appBase or the host's // configBase if (isDeletableResource(app, current)) { if (log.isDebugEnabled()) {
log.debug("Delete " + current);
}
ExpandWar.delete(current);
}
}
}
}
/* * Delete any resource that would trigger the automatic deployment code to * re-deploy the application. This means deleting: * - any resource located in the appBase * - any deployment descriptor located under the configBase * - symlinks in the appBase or configBase for either of the above
*/ privateboolean isDeletableResource(DeployedApplication app, File resource) { // The resource may be a file, a directory or a symlink to a file or // directory.
// Check that the resource is absolute. This should always be the case. if (!resource.isAbsolute()) {
log.warn(sm.getString("hostConfig.resourceNotAbsolute", app.name, resource)); returnfalse;
}
// Determine where the resource is located
String canonicalLocation; try {
canonicalLocation = resource.getParentFile().getCanonicalPath();
} catch (IOException e) {
log.warn(sm.getString( "hostConfig.canonicalizing", resource.getParentFile(), app.name), e); returnfalse;
}
if (canonicalLocation.equals(canonicalConfigBase) &&
resource.getName().endsWith(".xml")) { // Resource is an xml file in the configBase so it may be deleted returntrue;
}
// All other resources should not be deleted returnfalse;
}
publicvoid beforeStart() { if (host.getCreateDirs()) {
File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()}; for (File dir : dirs) { if (!dir.mkdirs() && !dir.isDirectory()) {
log.error(sm.getString("hostConfig.createDirs", dir));
}
}
}
}
/** * Process a "start" event for this Host.
*/ publicvoid start() {
if (log.isDebugEnabled()) {
log.debug(sm.getString("hostConfig.start"));
}
/** * Check status of all webapps.
*/ protectedvoid check() {
if (host.getAutoDeploy()) { // Check for resources modification to trigger redeployment
DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (DeployedApplication app : apps) { if (tryAddServiced(app.name)) { try {
checkResources(app, false);
--> --------------------
--> maximum size reached
--> --------------------
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.43Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.