org.glassfish.deployment.autodeploy.AutoDeployer Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2018-2019] [Payara Foundation and/or its affiliates]
/*
* AutoDeployer.java
*
*
* Created on February 19, 2003, 10:21 AM
*/
package org.glassfish.deployment.autodeploy;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import java.io.File;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.glassfish.logging.annotation.LogMessagesResourceBundle;
import org.glassfish.logging.annotation.LoggerInfo;
import com.sun.enterprise.util.LocalStringManagerImpl;
/**
* Handles the logic of deploying the module/app to the required destination.
* The destination is specified on the constructor and can be modified by calling setTarget(). The specific directory
* scanner can be set using setDirectoryScanner, default is AutoDeployDirectoryScanner
*
* @author vikas
* @author tjquinn
*/
public class AutoDeployer {
private final static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(AutoDeployer.class);
@LogMessagesResourceBundle
private static final String SHARED_LOGMESSAGE_RESOURCE = "org.glassfish.deployment.LogMessages";
// Reserve this range [NCLS-DEPLOYMENT-02001, NCLS-DEPLOYMENT-04000]
// for message ids used in this deployment autodeploy module
@LoggerInfo(subsystem = "DEPLOYMENT", description = "Deployment logger for autodeploy module", publish = true)
private static final String DEPLOYMENT_LOGGER = "javax.enterprise.system.tools.deployment.autodeploy";
public static final Logger deplLogger = Logger.getLogger(DEPLOYMENT_LOGGER, SHARED_LOGMESSAGE_RESOURCE);
@LogMessageInfo(message = "Exception caught: {0}", cause = "An exception was caught when the application was autodeployed.", action = "See the exception to determine how to fix the error", level = "SEVERE")
private static final String EXCEPTION_CAUGHT = "NCLS-DEPLOYMENT-02025";
@LogMessageInfo(message = "Autoundeploying application: {0}", level = "INFO")
private static final String AUTODEPLOYING_APPLICATION = "NCLS-DEPLOYMENT-02026";
@LogMessageInfo(message = "Selecting file {0} for autodeployment", level = "INFO")
private static final String SELECTING_FILE = "NCLS-DEPLOYMENT-02027";
private static final boolean DEFAULT_RENAME_ON_SUCCESS = true;
private static final boolean DEFAULT_FORCE_DEPLOY = true;
private static final boolean DEFAULT_INCLUDE_SUBDIR = false;
private static final boolean DEFAULT_ENABLED = true;
/*
* Represent the result of attempting autodeployment of a single file. PENDING indicates the file could not be opened as
* an archive, perhaps because the file was still in the process of being copied when the autodeployer tried to work
* with it. PENDING could also mean that the file has changed size since the last time the autodeployer checked it. It's
* then reasonable to think it might grow further, so autodeployer waits until the next time through to check it again.
*/
protected static final int DEPLOY_SUCCESS = 1;
protected static final int DEPLOY_FAILURE = 2;
protected static final int DEPLOY_PENDING = 3;
static final String STATUS_SUBDIR_PATH = ".autodeploystatus";
private Boolean verify;
private Boolean forceDeploy;
private Boolean enabled;
private Boolean jspPreCompilation;
private boolean renameOnSuccess = true;
private String target;
private File directory;
private String virtualServer;
private boolean cancelDeployment;
private AtomicBoolean inProgress = new AtomicBoolean(false);
private ServiceLocator serviceLocator;
private DirectoryScanner directoryScanner;
private AutodeployRetryManager retryManager;
private File domainRoot;
/**
* Creates a new autodeployer.
*
* @param target deployment target for autodeployed applications
* @param directoryPath directory to be scanned for changes
* @param virtualServer the virtual server to which to deploy apps to
* @param serviceLocator hk2 habitat for injection support
* @throws org.glassfish.deployment.autodeploy.AutoDeploymentException
*/
public AutoDeployer(String target, String directoryPath, String virtualServer, ServiceLocator serviceLocator) throws AutoDeploymentException {
this(target, directoryPath, virtualServer, false /* jspPreCompilation */, false /* verifierEnabled */, serviceLocator);
}
/**
* Creates a new instance of AutoDeployer
*
* @param target the deployment target for autodeployed applications
* @param directoryPath the directory to scan
* @param virtualServer the virtual server to deploy to
* @param jspPrecompilationEnabled whether to precompile JSPs
* @param verifierEnabled whether to verify applications during deployment
* @param renameOnSuccess rename the file if deployment is successful
* @param forceDeploy request that forced deployment occur if the app is already deployed
* @param enabled whether apps should be enabled upon auto-deployment
* @param serviceLocator HK2 serviceLocator for use in instantiating properly-init'd DeployCommand and UndeployCommand
* @throws org.glassfish.deployment.autodeploy.AutoDeploymentException
*/
public AutoDeployer(String target, String directoryPath, String virtualServer, boolean jspPrecompilationEnabled, boolean verifierEnabled,
boolean renameOnSuccess, boolean forceDeploy, boolean enabled, ServiceLocator serviceLocator) throws AutoDeploymentException {
setServiceLocator(serviceLocator);
setTarget(target);
setDirectory(directoryPath);
setJspPrecompilationEnabled(jspPrecompilationEnabled);
setVerifierEnabled(verifierEnabled);
setRenameOnSuccess(renameOnSuccess);
setForceDeploy(forceDeploy);
setVirtualServer(virtualServer);
setEnabled(enabled);
setRetryManager(serviceLocator);
}
public AutoDeployer(String target, String directoryPath, String virtualServer, boolean jspPrecompilationEnabled, boolean verifierEnabled,
ServiceLocator serviceLocator) throws AutoDeploymentException {
this(target, directoryPath, virtualServer, jspPrecompilationEnabled, verifierEnabled, DEFAULT_RENAME_ON_SUCCESS, DEFAULT_FORCE_DEPLOY, DEFAULT_ENABLED,
serviceLocator);
}
/**
* Sets the serviceLocator for use in creating DeployCommand and UndeployCommand instances.
*
* @param serviceLocator
*/
public void setServiceLocator(ServiceLocator serviceLocator) {
this.serviceLocator = serviceLocator;
}
/**
* Sets whether or not the precompileJSP option should be requested during autodeployments.
*
* @param setting true if JSPs should be precompiled during autodeployments
*/
public void setJspPrecompilationEnabled(boolean setting) {
jspPreCompilation = setting;
}
/**
* Sets the directory to be scanned by the autodeployer.
*
* @param directoryPath the directory path to scan
* @throws org.glassfish.deployment.autodeploy.AutoDeploymentException
*/
public void setDirectory(String directoryPath) throws AutoDeploymentException {
validateAutodeployDirectory(directoryPath);
this.directory = new File(directoryPath);
}
/**
* Sets whether descriptor verification should be requested during autodeployments.
*
* @param verify true if verification should occur during autodeployments
*/
public void setVerifierEnabled(boolean verify) {
this.verify = verify;
}
/**
* Creates the directories for the specified target, except that if the target is/would be a descendant of the base and
* the base does not exist don't create anything.
*
* This helps avoid a race condition in which the user stops the domain (which apparently reports success long before it
* actually finishes) then deletes the domain. The delete can run before the server has finished stopping. In some
* cases, the autodeployer has run in the meantime and
*
* @param baseDir
* @param dir
* @return true if the directory and all intervening ones were created; false otherwise
*/
boolean mkdirs(File baseDir, File targetDir) {
URI baseURI = baseDir.toURI().normalize();
URI targetURI = targetDir.toURI().normalize();
/*
* Go ahead and create all directories if the target falls outside the base OR if the target falls inside the base and
* the base exists.
*/
if (baseURI.relativize(targetURI).equals(targetURI) || baseDir.exists()) {
return targetDir.mkdirs();
}
/*
* The target would fall inside the base but the base does not exist.
*/
return false;
}
private void validateAutodeployDirectory(String autodeployDirPath) throws AutoDeploymentException {
File autodeployDir = new File(autodeployDirPath);
validateDirectory(autodeployDir);
validateDirectory(new File(autodeployDir, STATUS_SUBDIR_PATH));
}
private synchronized File domainRoot() {
if (domainRoot == null) {
domainRoot = serviceLocator.getService(ServerEnvironment.class).getInstanceRoot();
}
return domainRoot;
}
private void validateDirectory(File dirFile) throws AutoDeploymentException {
if (!dirFile.exists()) {
mkdirs(domainRoot(), dirFile);
} else {
if (!dirFile.isDirectory()) {
throw new AutoDeploymentException(
localStrings.getLocalString("enterprise.deployment.autodeploy.invalid_source_dir", "invalid source directory {0}", dirFile));
}
}
if (!dirFile.canRead()) {
throw new AutoDeploymentException(
localStrings.getLocalString("enterprise.deployment.autodeploy.dir_not_readable", "directory {0} not readable", dirFile));
}
if (!dirFile.canWrite()) {
throw new AutoDeploymentException(
localStrings.getLocalString("enterprise.deployment.autodeploy.dir_not_writeable", "directory {0} not writable", dirFile));
}
}
private void setRenameOnSuccess(boolean rename) {
renameOnSuccess = rename;
}
private void setForceDeploy(boolean force) {
forceDeploy = force;
}
private void setVirtualServer(String vs) {
virtualServer = vs;
}
private void setEnabled(boolean setting) {
enabled = setting;
}
/**
* set DirectoryScanner which will be used for filtering out deployeble component
*
* @param directoryScanner the new directory scanner to use
*/
public void setDirectoryScanner(DirectoryScanner directoryScanner) {
this.directoryScanner = directoryScanner;
}
/**
* set target server where the autual deployment will be done
*
* @param target
*/
public void setTarget(String target) {
this.target = target;
}
/**
* If an archive is successfully autodeployed, file will not be renamed to archive_deployed
*/
public void disableRenameOnSuccess() {
renameOnSuccess = false;
}
/**
* If an archive is successfully autodeployed will be renamed to archive_deployed
*/
public void enableRenameOnSuccess() {
// FIXME - Mahesh
renameOnSuccess = true;
}
/**
* Set whether this AutoDeployer should verify or not.
*
* @param verify whether to verify the app during deployment
*/
public void setVerify(boolean verify) {
this.verify = verify;
}
/**
* Set whether this AutoDeployer should precompile JSPs or not.
*
* @param jspPreCompilation precompilation setting
*/
public void setJspPreCompilation(boolean jspPreCompilation) {
this.jspPreCompilation = jspPreCompilation;
}
/**
* Run through the auto-deployment procedure.
*
* Clients should invoke this method to execute the auto-deployer once with the current configurable settings.
*/
public void run() {
if (directory.exists()) {
run(DEFAULT_INCLUDE_SUBDIR);
} else {
deplLogger.fine("autodeploy directory does not exist");
}
}
public synchronized void run(boolean includeSubdir) {
markInProgress();
try {
deployAll(directory, includeSubdir);
undeployAll(directory, includeSubdir);
} catch (AutoDeploymentException e) {
// print and continue
LogRecord logrecord = new LogRecord(SEVERE, EXCEPTION_CAUGHT);
logrecord.setParameters(new Object[] { e.getMessage() });
logrecord.setThrown(e);
deplLogger.log(logrecord);
} finally {
clearInProgress();
}
}
void init() {
cancelDeployment = false;
}
private void setRetryManager(ServiceLocator serviceLocator) {
retryManager = serviceLocator.getService(AutodeployRetryManager.class);
}
private void markInProgress() {
inProgress.set(true);
}
private void clearInProgress() {
inProgress.set(false);
notifyAll();
}
public synchronized void waitUntilIdle() throws InterruptedException {
while (!inProgress.get()) {
wait();
}
}
/**
* do deployment for all the deployable components in autoDeployDir dir.
*
* @return
*/
private void deployAll(File autoDeployDir, boolean includeSubDir) throws AutoDeploymentException {
// create with default scanner
if (directoryScanner == null) {
directoryScanner = new AutoDeployDirectoryScanner();
}
File[] files = null;
// get me all deployable entities
files = directoryScanner.getAllDeployableModules(autoDeployDir, includeSubDir);
/*
* To support slowly-copied files, the deploy method returns DEPLOY_SUCCESS if the file was successfully autodeployed
* DEPLOY_FAILURE if the file failed to be autodeployed DEPLOY_PENDING if the file needs to be tried again later
*
* The marker files should be updated only if the result is success or failure. So for each file make a separate
* decision about whether to record the result or not based on the result of the deploy method. Note that the boolean is
* initialized to true so that if an exception is thrown, the file's marker files will be updated.
*/
if (files != null && files.length > 0) {
deplLogger.log(Level.FINE, "Deployable files: {0}", Arrays.toString(files));
for (int i = 0; ((i < files.length) && !cancelDeployment); i++) {
boolean okToRecordResult = true;
try {
okToRecordResult = (deploy(files[i], autoDeployDir) != AutodeploymentStatus.PENDING);
} catch (AutoDeploymentException ae) {
// ignore and move to next file
} finally {
if (renameOnSuccess && okToRecordResult) {
deplLogger.log(Level.FINE, "Reporting deployed entity {0}", files[i].getAbsolutePath());
directoryScanner.deployedEntity(autoDeployDir, files[i]);
}
}
}
}
}
/**
* do undeployment for all deleted applications in autoDeployDir dir.
*
* @param autoDeployDir the directory to scan for deleted files
* @param includeSubdir
* @throws org.glassfish.deployment.autodeploy.AutoDeploymentException
*/
public void undeployAll(File autoDeployDir, boolean includeSubdir) throws AutoDeploymentException {
// create with default scanner
if (directoryScanner == null) {
directoryScanner = new AutoDeployDirectoryScanner();
}
File[] apps = null;
// get me all apps
apps = directoryScanner.getAllFilesForUndeployment(autoDeployDir, includeSubdir);
// deploying all applications
if (apps != null) {
for (int i = 0; i < apps.length && !cancelDeployment; i++) {
try {
undeploy(apps[i], getNameFromFilePath(autoDeployDir, apps[i]));
} catch (AutoDeploymentException ae) {
// ignore and move to next file
} finally {
// Mark the application as undeployed both in the case of success & failure.
directoryScanner.undeployedEntity(autoDeployDir, apps[i]);
}
}
}
///////// end for apps
}
private AutodeploymentStatus undeploy(File applicationFile, String name) throws AutoDeploymentException {
AutoUndeploymentOperation autoUndeploymentOperation = AutoUndeploymentOperation.newInstance(serviceLocator, applicationFile, name, target);
deplLogger.log(INFO, AUTODEPLOYING_APPLICATION, name);
return autoUndeploymentOperation.run();
}
/**
* Set cancel flag, which will ensure that only if there is any current deployment is in process, it will be completed
* but the deployer will not do any more deployment.
*
* @param value the cancel setting
*/
public void cancel(boolean value) {
cancelDeployment = value;
}
/**
* get cancel flag value
*
* @return
*/
public boolean isCancelled() {
return cancelDeployment;
}
/**
* Deploy any type of module.
*
* @param deployablefile the file to be deployed
* @param autodeployDir the directory where the file resides (holdover from earlier impl)
* @return status of the deployment attempt: DEPLOY_SUCCESS, DEPLOY_FAILURE, or DEPLOY_PENDING
* @throws AutoDeploymentException if any invoked method throws an exception
*/
protected AutodeploymentStatus deploy(File deployablefile, File autodeployDir) throws AutoDeploymentException {
String file = deployablefile.getAbsolutePath();
if (!retryManager.shouldAttemptDeployment(deployablefile)) {
return AutodeploymentStatus.PENDING;
}
deplLogger.log(INFO, SELECTING_FILE, file);
AutoDeploymentOperation ad = AutoDeploymentOperation.newInstance(serviceLocator, renameOnSuccess, deployablefile, enabled, virtualServer, forceDeploy, verify,
jspPreCompilation, target);
AutodeploymentStatus adStatus = ad.run();
return adStatus;
}
public static String getNameFromFilePath(File autodeployDir, File filePath) { // creating module name as file name
File parent = filePath.getParentFile();
String moduleName = null;
while (!parent.getAbsolutePath().equals(autodeployDir.getAbsolutePath())) {
if (moduleName == null) {
moduleName = parent.getName();
} else {
moduleName = parent.getName() + "_" + moduleName;
}
parent = parent.getParentFile();
}
if (moduleName == null) {
moduleName = filePath.getName();
} else {
moduleName = moduleName + "_" + filePath.getName();
}
int toIndex = moduleName.lastIndexOf('.');
if (toIndex > 0) {
moduleName = moduleName.substring(0, toIndex);
}
return moduleName;
}
public enum AutodeploymentStatus {
SUCCESS(true, ActionReport.ExitCode.SUCCESS, "enterprise.deployment.autodeploy.successfully_autodeployed", "deployment of {0} succeeded",
"enterprise.deployment.autodeploy.successfully_autoundeployed", "undeployment of {0} succeeded"),
FAILURE(false, ActionReport.ExitCode.FAILURE, "enterprise.deployment.autodeploy.autodeploy_failed", "deployment of {0} failed",
"enterprise.deployment.autodeploy.autoundeploy_failed", "undeployment of {0} failed"),
WARNING(true, ActionReport.ExitCode.WARNING, "enterprise.deployment.autodeploy.warning_autodeployed", "deployment of {0} succeeded with warning(s)",
"enterprise.deployment.autodeploy.warning_autoundeployed", "undeployment of {0} succeeded with warning(s)"),
PENDING(true, ActionReport.ExitCode.SUCCESS, "", "", "", "");
;
final boolean status;
final ActionReport.ExitCode exitCode;
final String deploymentMessageKey;
final String undeploymentMessageKey;
final String deploymentDefaultMessage;
final String undeploymentDefaultMessage;
AutodeploymentStatus(boolean status, ActionReport.ExitCode exitCode, String deploymentMessageKey, String deploymentDefaultMessage,
String undeploymentMessageKey, String undeploymentDefaultMessage) {
this.status = status;
this.exitCode = exitCode;
this.deploymentMessageKey = deploymentMessageKey;
this.deploymentDefaultMessage = deploymentDefaultMessage;
this.undeploymentMessageKey = undeploymentMessageKey;
this.undeploymentDefaultMessage = undeploymentDefaultMessage;
}
public static AutodeploymentStatus forExitCode(ActionReport.ExitCode exitCode) {
for (AutodeploymentStatus autodeploymentStatus : AutodeploymentStatus.values()) {
if (autodeploymentStatus.exitCode == exitCode) {
return autodeploymentStatus;
}
}
throw new IllegalArgumentException(exitCode.toString());
}
public ActionReport.ExitCode getExitCode() {
return exitCode;
}
}
}