/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.server.deployment.scanner; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ARCHIVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CANCELLED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_DEPLOYED_NOTIFICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_UNDEPLOYED_NOTIFICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OWNER; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PERSISTENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELATIVE_TO; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.URL; import static org.jboss.as.server.deployment.scanner.logging.DeploymentScannerLogger.ROOT_LOGGER; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream.Filter; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; import org.jboss.as.controller.ControlledProcessState; import org.jboss.as.controller.ControlledProcessStateService; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.notification.Notification; import org.jboss.as.controller.notification.NotificationHandler; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.server.deployment.DeploymentAddHandler; import org.jboss.as.server.deployment.DeploymentDeployHandler; import org.jboss.as.server.deployment.DeploymentFullReplaceHandler; import org.jboss.as.server.deployment.DeploymentRedeployHandler; import org.jboss.as.server.deployment.DeploymentRemoveHandler; import org.jboss.as.server.deployment.DeploymentUndeployHandler; import org.jboss.as.server.deployment.scanner.ZipCompletionScanner.NonScannableZipException; import org.jboss.as.server.deployment.scanner.api.DeploymentOperations; import org.jboss.as.server.deployment.scanner.api.DeploymentScanner; import org.jboss.as.server.deployment.scanner.logging.DeploymentScannerLogger; import org.jboss.dmr.ModelNode; import org.jboss.dmr.Property; /** * Service that monitors the filesystem for deployment content and if found deploys it. * * @author Brian Stansberry */ class FileSystemDeploymentService implements DeploymentScanner, NotificationHandler { static final Pattern ARCHIVE_PATTERN = Pattern.compile("^.*\\.(?:(?:[SsWwJjEeRr][Aa][Rr])|(?:[Ww][Aa][Bb])|(?:[Ee][Ss][Aa]))$"); static final String DEPLOYED = ".deployed"; static final String FAILED_DEPLOY = ".failed"; static final String DO_DEPLOY = ".dodeploy"; static final String DEPLOYING = ".isdeploying"; static final String UNDEPLOYING = ".isundeploying"; static final String UNDEPLOYED = ".undeployed"; static final String SKIP_DEPLOY = ".skipdeploy"; static final String PENDING = ".pending"; static final String WEB_INF = "WEB-INF"; static final String META_INF = "META-INF"; /** * Max period an incomplete auto-deploy file can have no change in content */ static final long MAX_NO_PROGRESS = 60000; /** * Default timeout for deployments to execute in seconds */ static final long DEFAULT_DEPLOYMENT_TIMEOUT = 600; private File deploymentDir; private long scanInterval = 0; private volatile boolean scanEnabled = false; private volatile boolean firstScan = true; private volatile boolean deployedContentEstablished = false; private ScheduledFuture<?> scanTask; private ScheduledFuture<?> rescanIncompleteTask; private ScheduledFuture<?> rescanUndeployTask; private final Lock scanLock = new ReentrantLock(); private final Map<String, DeploymentMarker> deployed = new HashMap<String, DeploymentMarker>(); private final HashSet<String> ignoredMissingDeployments = new HashSet<String>(); private final HashSet<String> noticeLogged = new HashSet<String>(); private final HashSet<String> illegalDirLogged = new HashSet<String>(); private final HashSet<String> prematureExplodedContentDeletionLogged = new HashSet<String>(); private final HashSet<File> nonscannableLogged = new HashSet<File>(); private final Map<File, IncompleteDeploymentStatus> incompleteDeployments = new HashMap<File, IncompleteDeploymentStatus>(); private final ScheduledExecutorService scheduledExecutor; private final ControlledProcessStateService processStateService; private volatile DeploymentOperations.Factory deploymentOperationsFactory; private volatile DeploymentOperations deploymentOperations; private Filter<Path> filter = new ExtensibleFilter(); private volatile boolean autoDeployZip; private volatile boolean autoDeployExploded; private volatile boolean autoDeployXml; private volatile long maxNoProgress = MAX_NO_PROGRESS; private volatile boolean rollbackOnRuntimeFailure; private volatile long deploymentTimeout = DEFAULT_DEPLOYMENT_TIMEOUT; private final ModelNode resourceAddress; private final String relativeTo; private final String relativePath; private final PropertyChangeListener propertyChangeListener; private Future<?> undeployScanTask; private volatile boolean deploymentDirAccessible = true; private volatile boolean lastScanSuccessful = true; @Override public void handleNotification(Notification notification) { if (acquireScanLock()) { try { switch (notification.getType()) { case DEPLOYMENT_DEPLOYED_NOTIFICATION: { String runtimeName = notification.getData().get(DEPLOYMENT).asString(); if (!deployed.containsKey(runtimeName)) { updateStatusAfterDeploymentNotification(deploymentDir.toPath(), runtimeName); } break; } case DEPLOYMENT_UNDEPLOYED_NOTIFICATION: { String runtimeName = notification.getData().get(DEPLOYMENT).asString(); if (deployed.containsKey(runtimeName)) { clearMarkers(deployed.get(runtimeName).parentFolder.toPath(), runtimeName); updateStatusAfterUndeploymentNotification(deploymentDir.toPath(), runtimeName); deployed.remove(runtimeName); } break; } default: //ignore } } finally { releaseScanLock(); } } } private void updateStatusAfterUndeploymentNotification(Path dir, String runtimeName) { ROOT_LOGGER.debugf("Updating status after undeployment notification for %s", runtimeName); Path undeployedMarker = dir.resolve(runtimeName + UNDEPLOYED); final Path deploymentFile = dir.resolve(runtimeName); if (!Files.exists(undeployedMarker) && Files.exists(deploymentFile)) { try { Files.createFile(undeployedMarker); } catch (IOException ioex) { ROOT_LOGGER.errorWritingDeploymentMarker(ioex, undeployedMarker.toString()); } } } private void updateStatusAfterDeploymentNotification(Path dir, String runtimeName) { ROOT_LOGGER.debugf("Updating status after deployment notification for %s", runtimeName); Path undeployedMarker = dir.resolve(runtimeName + UNDEPLOYED); Path deployedMarker = dir.resolve(runtimeName + DEPLOYED); if (Files.exists(undeployedMarker)) { try { Files.delete(undeployedMarker); } catch (IOException ioex) { ROOT_LOGGER.cannotRemoveDeploymentMarker(undeployedMarker.toFile()); } } final Path deploymentFile = dir.resolve(runtimeName); if (!Files.exists(deployedMarker) && Files.exists(deploymentFile)) { try { deployedMarker = Files.createFile(deployedMarker); boolean isArchive = Files.isRegularFile(deploymentFile); if (Files.exists(deploymentFile)) { Files.setLastModifiedTime(deployedMarker, Files.getLastModifiedTime(deploymentFile)); } deployed.put(runtimeName, new DeploymentMarker(Files.getLastModifiedTime(deployedMarker).toMillis(), isArchive, dir.toFile())); } catch (IOException ioex) { ROOT_LOGGER.errorWritingDeploymentMarker(ioex, deployedMarker.toString()); } } } private void clearMarkers(Path dir, String runtimeName) { String fileName = runtimeName + DO_DEPLOY; try { Files.deleteIfExists(dir.resolve(fileName)); fileName = runtimeName + FAILED_DEPLOY; Files.deleteIfExists(dir.resolve(fileName)); fileName = runtimeName + SKIP_DEPLOY; Files.deleteIfExists(dir.resolve(fileName)); fileName = runtimeName + DEPLOYED; Files.deleteIfExists(dir.resolve(fileName)); } catch (IOException ioex) { ROOT_LOGGER.cannotRemoveDeploymentMarker(fileName); } } private class DeploymentScanRunnable implements Runnable { @Override public void run() { try { scan(); } catch (RejectedExecutionException e) { //Do nothing as this happens if a scan occurs during a reload of shutdown of a server. } catch (Exception e) { ROOT_LOGGER.scanException(e, deploymentDir.getAbsolutePath()); } } } private final DeploymentScanRunnable scanRunnable = new DeploymentScanRunnable(); FileSystemDeploymentService(final PathAddress resourceAddress, final String relativeTo, final File deploymentDir, final File relativeToDir, final DeploymentOperations.Factory deploymentOperationsFactory, final ScheduledExecutorService scheduledExecutor, final ControlledProcessStateService processStateService) throws OperationFailedException { assert resourceAddress != null; assert resourceAddress.size() > 0; assert scheduledExecutor != null; assert deploymentDir != null; this.resourceAddress = resourceAddress.toModelNode(); this.resourceAddress.protect(); this.relativeTo = relativeTo; this.deploymentDir = deploymentDir; this.deploymentOperationsFactory = deploymentOperationsFactory; this.scheduledExecutor = scheduledExecutor; this.processStateService = processStateService; if(processStateService != null) { this.propertyChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (ControlledProcessState.State.RUNNING == evt.getNewValue()) { synchronized (this) { if (scanEnabled) { undeployScanTask = scheduledExecutor.submit(new UndeployScanRunnable()); } } } else if (ControlledProcessState.State.STOPPING == evt.getNewValue()) { //let's prevent the starting of a new scan scanEnabled = false; if(undeployScanTask != null) { undeployScanTask.cancel(true); undeployScanTask = null; } processStateService.removePropertyChangeListener(propertyChangeListener); } } }; this.processStateService.addPropertyChangeListener(propertyChangeListener); } else { this.propertyChangeListener = null; } if (relativeToDir != null) { String fullDir = deploymentDir.getAbsolutePath(); String relDir = relativeToDir.getAbsolutePath(); String sub = fullDir.substring(relDir.length()); if (sub.startsWith(File.separator)) { sub = sub.length() == 1 ? "" : sub.substring(1); } this.relativePath = sub.length() > 0 ? sub + File.separator : sub; } else { relativePath = null; } } @Override public boolean isAutoDeployZippedContent() { return autoDeployZip; } @Override public void setAutoDeployZippedContent(boolean autoDeployZip) { this.autoDeployZip = autoDeployZip; } @Override public boolean isAutoDeployExplodedContent() { return autoDeployExploded; } @Override public void setAutoDeployExplodedContent(boolean autoDeployExploded) { if (autoDeployExploded && !this.autoDeployExploded) { ROOT_LOGGER.explodedAutoDeploymentContentWarning(DO_DEPLOY, CommonAttributes.AUTO_DEPLOY_EXPLODED); } this.autoDeployExploded = autoDeployExploded; } @Override public void setAutoDeployXMLContent(final boolean autoDeployXML) { this.autoDeployXml = autoDeployXML; } @Override public boolean isAutoDeployXMLContent() { return autoDeployXml; } @Override public boolean isEnabled() { return scanEnabled; } @Override public long getScanInterval() { return scanInterval; } @Override public synchronized void setScanInterval(long scanInterval) { if (scanInterval != this.scanInterval) { cancelScan(); } this.scanInterval = scanInterval; startScan(); } @Override public void setDeploymentTimeout(long deploymentTimeout) { this.deploymentTimeout = deploymentTimeout; } @Override public synchronized void startScanner() { assert deploymentOperationsFactory != null : "deploymentOperationsFactory is null"; startScanner(deploymentOperationsFactory.create()); } @Override public void setRuntimeFailureCausesRollback(boolean rollbackOnRuntimeFailure) { this.rollbackOnRuntimeFailure = rollbackOnRuntimeFailure; } /** * {@inheritDoc} */ @Override public synchronized void startScanner(final DeploymentOperations deploymentOperations) { this.deploymentOperations = deploymentOperations; final boolean scanEnabled = this.scanEnabled; if (scanEnabled) { return; } this.scanEnabled = true; startScan(); ROOT_LOGGER.started(getClass().getSimpleName(), deploymentDir.getAbsolutePath()); } /** * {@inheritDoc} */ @Override public synchronized void stopScanner() { this.scanEnabled = false; cancelScan(); safeClose(deploymentOperations); this.deploymentOperations = null; if (undeployScanTask != null) { undeployScanTask.cancel(true); } this.undeployScanTask = null; } /** Allow DeploymentScannerService to set the factory on the boot-time scanner */ void setDeploymentOperationsFactory(final DeploymentOperations.Factory factory) { assert factory != null : "factory is null"; this.deploymentOperationsFactory = factory; } /** * Hook solely for unit test to control how long deployments with no progress can exist without failing */ void setMaxNoProgress(long max) { this.maxNoProgress = max; } private void establishDeployedContentList(File dir, final DeploymentOperations deploymentOperations) { final Set<String> deploymentNames = deploymentOperations.getDeploymentsStatus().keySet(); final List<File> children = listDirectoryChildren(dir); for (File child : children) { final String fileName = child.getName(); if (child.isDirectory()) { if (!isEEArchive(fileName)) { establishDeployedContentList(child, deploymentOperations); } } else if (fileName.endsWith(DEPLOYED)) { final String deploymentName = fileName.substring(0, fileName.length() - DEPLOYED.length()); if (deploymentNames.contains(deploymentName)) { File deployment = new File(dir, deploymentName); deployed.put(deploymentName, new DeploymentMarker(child.lastModified(), !deployment.isDirectory(), dir)); } else { if (!child.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(fileName); } // AS7-1130 Put down a marker so we deploy on first scan File skipDeploy = new File(dir, deploymentName + SKIP_DEPLOY); if (!skipDeploy.exists()) { final File deployedMarker = new File(dir, deploymentName + DO_DEPLOY); createMarkerFile(deployedMarker, deploymentName); } } } } } /** Perform a one-off scan during boot to establish deployment tasks to execute during boot */ void bootTimeScan(final DeploymentOperations deploymentOperations) { // WFCORE-1579: skip the scan if deployment dir is not available if (!checkDeploymentDir(this.deploymentDir)) { DeploymentScannerLogger.ROOT_LOGGER.bootTimeScanFailed(deploymentDir.getAbsolutePath()); return; } this.establishDeployedContentList(this.deploymentDir, deploymentOperations); deployedContentEstablished = true; if (acquireScanLock()) { try { scan(true, deploymentOperations); } finally { releaseScanLock(); } } } /** Perform a one-off scan to establish deployment tasks */ void singleScan() { assert deploymentOperationsFactory != null : "deploymentOperationsFactory is null"; ManualScanCallable manualScan = new ManualScanCallable(); scheduledExecutor.submit(manualScan); } /** Perform a normal scan */ void scan() { if (acquireScanLock()) { boolean scheduleRescan = false; try { scheduleRescan = scan(false, deploymentOperations); } finally { try { if (scheduleRescan) { synchronized (this) { if (scanEnabled) { rescanIncompleteTask = scheduledExecutor.schedule(scanRunnable, 200, TimeUnit.MILLISECONDS); } } } } finally { releaseScanLock(); } } } } /** * Perform a post-boot scan to remove any deployments added during boot that failed to deploy properly. * This method isn't private solely to allow a unit test in the same package to call it. */ void forcedUndeployScan() { if (acquireScanLock()) { try { ROOT_LOGGER.tracef("Performing a post-boot forced undeploy scan for scan directory %s", deploymentDir.getAbsolutePath()); ScanContext scanContext = new ScanContext(deploymentOperations); // Add remove actions to the plan for anything we count as // deployed that we didn't find on the scan for (Map.Entry<String, DeploymentMarker> missing : scanContext.toRemove.entrySet()) { // remove successful deployment and left will be removed if (scanContext.registeredDeployments.containsKey(missing.getKey())) { scanContext.registeredDeployments.remove(missing.getKey()); } } Set<String> scannedDeployments = new HashSet<String>(scanContext.registeredDeployments.keySet()); scannedDeployments.removeAll(scanContext.persistentDeployments); List<ScannerTask> scannerTasks = scanContext.scannerTasks; for (String toUndeploy : scannedDeployments) { scannerTasks.add(new UndeployTask(toUndeploy, deploymentDir, scanContext.scanStartTime, true)); } try { executeScannerTasks(scannerTasks, deploymentOperations, true); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } ROOT_LOGGER.tracef("Forced undeploy scan complete"); } catch (Exception e) { ROOT_LOGGER.scanException(e, deploymentDir.getAbsolutePath()); } finally { releaseScanLock(); } } } private boolean acquireScanLock() { try { scanLock.lockInterruptibly(); return true; } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return false; } } private void releaseScanLock() { scanLock.unlock(); } private boolean scan(boolean oneOffScan, final DeploymentOperations deploymentOperations) { boolean scheduleRescan = false; if (scanEnabled || oneOffScan) { // confirm the scan is still wanted ROOT_LOGGER.tracef("Scanning directory %s for deployment content changes", deploymentDir.getAbsolutePath()); // WFCORE-1579: skip the scan if deployment dir is not available if (!checkDeploymentDir(deploymentDir)) { if (lastScanSuccessful) { lastScanSuccessful = false; ROOT_LOGGER.scanFailed(deploymentDir.getAbsolutePath()); } return scheduleRescan; } // if deployed content list was not established during scanner start (due to inaccessible deployment dir), // do it now if (!deployedContentEstablished) { establishDeployedContentList(deploymentDir, deploymentOperations); deployedContentEstablished = true; } ScanContext scanContext = null; try { scanContext = new ScanContext(deploymentOperations); } catch (RuntimeException ex) { //scanner has stoppped in the meanwhile so we don't need to pursue if (!scanEnabled) { return scheduleRescan; } throw ex; } scanDirectory(deploymentDir, relativePath, scanContext); // WARN about markers with no associated content. Do this first in case any auto-deploy issue // is due to a file that wasn't meant to be auto-deployed, but has a misspelled marker ignoredMissingDeployments.retainAll(scanContext.ignoredMissingDeployments); for (String deploymentName : scanContext.ignoredMissingDeployments) { if (ignoredMissingDeployments.add(deploymentName)) { ROOT_LOGGER.deploymentNotFound(deploymentName); } } // Log INFO about non-auto-deploy files that have no marker files noticeLogged.retainAll(scanContext.nonDeployable); for (String fileName : scanContext.nonDeployable) { if (noticeLogged.add(fileName)) { ROOT_LOGGER.deploymentTriggered(fileName, DO_DEPLOY); } } // Log ERROR about META-INF and WEB-INF dirs outside a deployment illegalDirLogged.retainAll(scanContext.illegalDir); for (String fileName : scanContext.illegalDir) { if (illegalDirLogged.add(fileName)) { ROOT_LOGGER.invalidExplodedDeploymentDirectory(fileName, deploymentDir.getAbsolutePath()); } } // Log about deleting exploded deployments without first triggering undeploy by deleting .deployed prematureExplodedContentDeletionLogged.retainAll(scanContext.prematureExplodedDeletions); for (String fileName : scanContext.prematureExplodedDeletions) { if (prematureExplodedContentDeletionLogged.add(fileName)) { ROOT_LOGGER.explodedDeploymentContentDeleted(fileName, DEPLOYED); } } // Deal with any incomplete or non-scannable auto-deploy content ScanStatus status = handleAutoDeployFailures(scanContext); if (status != ScanStatus.PROCEED) { if (status == ScanStatus.RETRY && scanInterval > 1000) { // schedule a non-repeating task to try again more quickly scheduleRescan = true; } } else { List<ScannerTask> scannerTasks = scanContext.scannerTasks; // Add remove actions to the plan for anything we count as // deployed that we didn't find on the scan for (Map.Entry<String, DeploymentMarker> missing : scanContext.toRemove.entrySet()) { scannerTasks.add(new UndeployTask(missing.getKey(), missing.getValue().parentFolder, scanContext.scanStartTime, false)); } try { executeScannerTasks(scannerTasks, deploymentOperations, oneOffScan); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } ROOT_LOGGER.tracef("Scan complete"); firstScan = false; } } return scheduleRescan; } private void executeScannerTasks(List<ScannerTask> scannerTasks, DeploymentOperations deploymentOperations, boolean oneOffScan) throws InterruptedException { // Process the tasks if (scannerTasks.size() > 0) { List<ModelNode> updates = new ArrayList<ModelNode>(scannerTasks.size()); for (ScannerTask task : scannerTasks) { task.recordInProgress(); // puts down .isdeploying, .isundeploying final ModelNode update = task.getUpdate(); if (ROOT_LOGGER.isDebugEnabled()) { ROOT_LOGGER.debugf("Deployment scan of [%s] found update action [%s]", deploymentDir, update); } updates.add(update); } boolean first = true; while (!updates.isEmpty() && (first || !oneOffScan)) { first = false; final ModelNode results; try { final Future<ModelNode> futureResults = deploymentOperations.deploy(getCompositeUpdate(updates), scheduledExecutor); try { results = futureResults.get(deploymentTimeout, TimeUnit.SECONDS); } catch (TimeoutException e) { futureResults.cancel(true); final ModelNode failure = new ModelNode(); failure.get(OUTCOME).set(FAILED); failure.get(FAILURE_DESCRIPTION).set(DeploymentScannerLogger.ROOT_LOGGER.deploymentTimeout(deploymentTimeout)); for (ScannerTask task : scannerTasks) { task.handleFailureResult(failure); } break; } catch (InterruptedException e) { futureResults.cancel(true); throw e; } catch (Exception e) { ROOT_LOGGER.fileSystemDeploymentFailed(e); futureResults.cancel(true); final ModelNode failure = new ModelNode(); failure.get(OUTCOME).set(FAILED); failure.get(FAILURE_DESCRIPTION).set(e.getMessage()); for (ScannerTask task : scannerTasks) { task.handleFailureResult(failure); } break; } } catch(RejectedExecutionException ex) { //The executor was closed and no task could be submitted. for (ScannerTask task : scannerTasks) { task.removeInProgressMarker(); } break; } final List<ModelNode> toRetry = new ArrayList<ModelNode>(); final List<ScannerTask> retryTasks = new ArrayList<ScannerTask>(); if (results.hasDefined(RESULT)) { final List<Property> resultList = results.get(RESULT).asPropertyList(); for (int i = 0; i < resultList.size(); i++) { final ModelNode result = resultList.get(i).getValue(); final ScannerTask task = scannerTasks.get(i); final ModelNode outcome = result.get(OUTCOME); StringBuilder failureDesc = new StringBuilder(); if (outcome.isDefined() && SUCCESS.equals(outcome.asString()) && handleCompositeResult(result, failureDesc)){ task.handleSuccessResult(); } else if (outcome.isDefined() && CANCELLED.equals(outcome.asString())) { toRetry.add(updates.get(i)); retryTasks.add(task); } else { if (failureDesc.length() > 0) { result.get(FAILURE_DESCRIPTION).set(failureDesc.toString()); } task.handleFailureResult(result); } } updates = toRetry; scannerTasks = retryTasks; } else { for (ScannerTask current : scannerTasks) { current.handleFailureResult(results); } } } } } private class ManualScanCallable implements Runnable { @Override public void run() { DeploymentOperations operations = deploymentOperations; if (operations == null) { operations = deploymentOperationsFactory.create(); } if (acquireScanLock()) { try { DeploymentScannerLogger.ROOT_LOGGER.debug("Manual scan launched"); scan(true, operations); } catch (Exception e) { ROOT_LOGGER.scanException(e, deploymentDir.getAbsolutePath()); } finally { releaseScanLock(); } } } } private class UndeployScanRunnable implements Runnable { @Override public void run() { forcedUndeployScan(); processStateService.removePropertyChangeListener(propertyChangeListener); } } private boolean handleCompositeResult(ModelNode resultNode, StringBuilder failureDesc) { // WFLY-1305, regardless rollback-on-runtime-failure option, check each composite step result ModelNode outcome = resultNode.get(OUTCOME); boolean success = true; if (resultNode.get(OUTCOME).isDefined() && SUCCESS.equals(outcome.asString())) { if (resultNode.get(RESULT).isDefined()) { List<Property> results = resultNode.get(RESULT).asPropertyList(); for (int i = 0; i < results.size(); i++) { if (!handleCompositeResult(results.get(i).getValue(), failureDesc)) { success = false; break; } } } } else { success = false; if (resultNode.get(FAILURE_DESCRIPTION).isDefined()) { failureDesc.append(resultNode.get(FAILURE_DESCRIPTION).toString()); } } return success; } /** * Checks that given directory if readable & writable and prints a warning if the check fails. Warning is only * printed once and is not repeated until the condition is fixed and broken again. * * @param directory deployment directory * @return does given directory exist and is readable and writable? */ private boolean checkDeploymentDir(File directory) { if (!directory.exists()) { if (deploymentDirAccessible) { deploymentDirAccessible = false; ROOT_LOGGER.directoryIsNonexistent(deploymentDir.getAbsolutePath()); } } else if (!directory.isDirectory()) { if (deploymentDirAccessible) { deploymentDirAccessible = false; ROOT_LOGGER.isNotADirectory(deploymentDir.getAbsolutePath()); } } else if (!directory.canRead()) { if (deploymentDirAccessible) { deploymentDirAccessible = false; ROOT_LOGGER.directoryIsNotReadable(deploymentDir.getAbsolutePath()); } } else if (!directory.canWrite()) { if (deploymentDirAccessible) { deploymentDirAccessible = false; ROOT_LOGGER.directoryIsNotWritable(deploymentDir.getAbsolutePath()); } } else { deploymentDirAccessible = true; } return deploymentDirAccessible; } /** * Scan the given directory for content changes. * * @param directory the directory to scan * @param scanContext context of the scan */ private void scanDirectory(final File directory, final String relativePath, final ScanContext scanContext) { final List<File> children = listDirectoryChildren(directory, filter); for (File child : children) { final String fileName = child.getName(); if (fileName.endsWith(DEPLOYED)) { final String deploymentName = fileName.substring(0, fileName.length() - DEPLOYED.length()); DeploymentMarker deploymentMarker = deployed.get(deploymentName); if (deploymentMarker == null) { scanContext.toRemove.remove(deploymentName); removeExtraneousMarker(child, fileName); } else { final File deploymentFile = new File(directory, deploymentName); if (deploymentFile.exists()) { scanContext.toRemove.remove(deploymentName); if (deployed.get(deploymentName).lastModified != child.lastModified()) { scanContext.scannerTasks.add(new RedeployTask(deploymentName, child.lastModified(), directory, !child.isDirectory())); } else { // AS7-784 check for undeploy or removal of the deployment via another management client Boolean isDeployed = scanContext.registeredDeployments.get(deploymentName); if (isDeployed == null || !isDeployed) { // It was undeployed or removed; get rid of the .deployed marker and // put down a .undeployed marker so we don't process this again deployed.remove(deploymentName); removeExtraneousMarker(child, fileName); final File marker = new File(directory, deploymentName + UNDEPLOYED); createMarkerFile(marker, deploymentName); if (isDeployed == null) { DeploymentScannerLogger.ROOT_LOGGER.scannerDeploymentRemovedButNotByScanner(deploymentName, marker); } else { DeploymentScannerLogger.ROOT_LOGGER.scannerDeploymentUndeployedButNotByScanner(deploymentName, marker); } } } } else { boolean autoDeployable = deploymentMarker.archive ? autoDeployZip : autoDeployExploded; if (!autoDeployable) { // Don't undeploy but log a warn if this is exploded content scanContext.toRemove.remove(deploymentName); if (!deploymentMarker.archive) { scanContext.prematureExplodedDeletions.add(deploymentName); } } // else AS7-1240 -- content is gone, leave deploymentName in scanContext.toRemove to trigger undeploy } } } else if (fileName.endsWith(DO_DEPLOY) || (fileName.endsWith(FAILED_DEPLOY) && firstScan)) { // AS7-2581 - attempt to redeploy failed deployments on restart. final String markerStatus = fileName.endsWith(DO_DEPLOY) ? DO_DEPLOY : FAILED_DEPLOY; final String deploymentName = fileName.substring(0, fileName.length() - markerStatus.length()); if (FAILED_DEPLOY.equals(markerStatus)) { if (!scanContext.firstScanDeployments.add(deploymentName)) { continue; } ROOT_LOGGER.reattemptingFailedDeployment(deploymentName); } final File deploymentFile = new File(directory, deploymentName); if (!deploymentFile.exists()) { scanContext.ignoredMissingDeployments.add(deploymentName); continue; } long timestamp = getDeploymentTimestamp(deploymentFile); final String path = relativeTo == null ? deploymentFile.getAbsolutePath() : relativePath + deploymentName; // TODO: // sub-directories // in // the // deploymentDir final boolean archive = deploymentFile.isFile(); addContentAddingTask(path, archive, deploymentName, deploymentFile, timestamp, scanContext); } else if (fileName.endsWith(FAILED_DEPLOY)) { final String deploymentName = fileName.substring(0, fileName.length() - FAILED_DEPLOY.length()); scanContext.toRemove.remove(deploymentName); if (!deployed.containsKey(deploymentName) && !(new File(child.getParent(), deploymentName).exists())) { removeExtraneousMarker(child, fileName); } } else if (isEEArchive(fileName)) { boolean autoDeployable = child.isDirectory() ? autoDeployExploded : autoDeployZip; if (autoDeployable) { if (!isAutoDeployDisabled(child)) { long timestamp = getDeploymentTimestamp(child); synchronizeScannerStatus(scanContext, directory, fileName, timestamp); if (isFailedOrUndeployed(scanContext, directory, fileName, timestamp) || scanContext.firstScanDeployments.contains(fileName)) { continue; } DeploymentMarker marker = deployed.get(fileName); if (marker == null || marker.lastModified != timestamp) { try { if (isZipComplete(child)) { final String path = relativeTo == null ? child.getAbsolutePath() : relativePath + fileName; final boolean archive = child.isFile(); if(firstScan){ scanContext.firstScanDeployments.add(fileName); } addContentAddingTask(path, archive, fileName, child, timestamp, scanContext); } else { //we need to make sure that the file was not deleted while //the scanner was running if (child.exists()) { scanContext.incompleteFiles.put(child, new IncompleteDeploymentStatus(child, timestamp)); } } } catch (NonScannableZipException e) { // Track for possible logging in scan() scanContext.nonscannable.put(child, new NonScannableStatus(e, timestamp)); } } } } else if (!deployed.containsKey(fileName) && !new File(fileName + DO_DEPLOY).exists() && !new File(fileName + FAILED_DEPLOY).exists()) { // Track for possible INFO logging of the need for a marker scanContext.nonDeployable.add(fileName); } } else if (isXmlFile(fileName)) { if (autoDeployXml) { if (!isAutoDeployDisabled(child)) { long timestamp = getDeploymentTimestamp(child); if (isFailedOrUndeployed(scanContext, directory, fileName, timestamp) || scanContext.firstScanDeployments.contains(fileName)) { continue; } DeploymentMarker marker = deployed.get(fileName); if (marker == null || marker.lastModified != timestamp) { if (isXmlComplete(child)) { final String path = relativeTo == null ? child.getAbsolutePath() : relativePath + fileName; if(firstScan){ scanContext.firstScanDeployments.add(fileName); } addContentAddingTask(path, true, fileName, child, timestamp, scanContext); } else { //we need to make sure that the file was not deleted while //the scanner was running if (child.exists()) { scanContext.incompleteFiles.put(child, new IncompleteDeploymentStatus(child, timestamp)); } } } } } else if (!deployed.containsKey(fileName) && !new File(fileName + DO_DEPLOY).exists() && !new File(fileName + FAILED_DEPLOY).exists()) { // Track for possible INFO logging of the need for a marker scanContext.nonDeployable.add(fileName); } } else if (fileName.endsWith(DEPLOYING) || fileName.endsWith(UNDEPLOYING)) { // These markers should not survive a scan removeExtraneousMarker(child, fileName); } else if (fileName.endsWith(PENDING)) { // Do some housekeeping if the referenced deployment is gone final String deploymentName = fileName.substring(0, fileName.length() - PENDING.length()); File deployment = new File(child.getParent(), deploymentName); if (!deployment.exists()) { removeExtraneousMarker(child, fileName); } } else if (child.isDirectory()) { // exploded deployments would have been caught by isEEArchive(fileName) above if (WEB_INF.equalsIgnoreCase(fileName) || META_INF.equalsIgnoreCase(fileName)) { // Looks like someone unzipped an archive in the scanned dir // Track for possible ERROR logging scanContext.illegalDir.add(fileName); } else { scanDirectory(child, relativePath + child.getName() + File.separator, scanContext); } } } } private boolean isXmlComplete(final File xmlFile) { try { return XmlCompletionScanner.isCompleteDocument(xmlFile); } catch (Exception e) { ROOT_LOGGER.failedCheckingXMLFile(e, xmlFile.getPath()); return false; } } private boolean isFailedOrUndeployed(final ScanContext scanContext, final File directory, final String fileName, final long timestamp) { final File failedMarker = new File(directory, fileName + FAILED_DEPLOY); if (failedMarker.exists() && timestamp <= failedMarker.lastModified()) { return true; } final File undeployedMarker = new File(directory, fileName + UNDEPLOYED); if (isMarkedUndeployed(undeployedMarker, timestamp) && !isRegisteredDeployment(scanContext, fileName)) { return true; } return false; } private void synchronizeScannerStatus(ScanContext scanContext, File directory, String fileName, long timestamp) { if (isRegisteredDeployment(scanContext, fileName)) { final File undeployedMarker = new File(directory, fileName + UNDEPLOYED); if (isMarkedUndeployed(undeployedMarker, timestamp) && !scanContext.persistentDeployments.contains(fileName)) { try { ROOT_LOGGER.scannerDeploymentRedeployedButNotByScanner(fileName, undeployedMarker); //We have a deployed app with an undeployed marker undeployedMarker.delete(); final File deployedMarker = new File(directory, fileName + DEPLOYED); deployedMarker.createNewFile(); boolean isArchive = false; if (deployed.containsKey(fileName)) { isArchive = deployed.get(fileName).archive; deployedMarker.setLastModified(deployed.get(fileName).lastModified); } else { final File deploymentFile = new File(directory, fileName); isArchive = deploymentFile.exists() && deploymentFile.isFile(); if(deploymentFile.exists()) { deployedMarker.setLastModified(deploymentFile.lastModified()); } } deployed.put(fileName, new DeploymentMarker(deployedMarker.lastModified(), isArchive, directory)); } catch (IOException ex) { ROOT_LOGGER.failedStatusSynchronization(ex, fileName); } } } } private long addContentAddingTask(final String path, final boolean archive, final String deploymentName, final File deploymentFile, final long timestamp, final ScanContext scanContext) { if (scanContext.registeredDeployments.containsKey(deploymentName)) { scanContext.scannerTasks.add(new ReplaceTask(path, archive, deploymentName, deploymentFile, timestamp)); } else { scanContext.scannerTasks.add(new DeployTask(path, archive, deploymentName, deploymentFile, timestamp)); } scanContext.toRemove.remove(deploymentName); return timestamp; } private boolean isRegisteredDeployment(final ScanContext scanContext, final String fileName) { if(!scanContext.persistentDeployments.contains(fileName)) {//check that we are talking about the deployment in the scanned folder return scanContext.registeredDeployments.get(fileName) == null ? false : scanContext.registeredDeployments.get(fileName); } return false; } private boolean isMarkedUndeployed(final File undeployedMarker, final long timestamp) { return undeployedMarker.exists() && timestamp <= undeployedMarker.lastModified(); } private boolean isZipComplete(File file) throws NonScannableZipException { if (file.isDirectory()) { for (File child : listDirectoryChildren(file)) { if (!isZipComplete(child)) { return false; } } return true; } else if (isEEArchive(file.getName())) { try { return ZipCompletionScanner.isCompleteZip(file); } catch (IOException e) { ROOT_LOGGER.failedCheckingZipFile(e, file.getPath()); return false; } } else { // A non-zip child return true; } } private boolean isAutoDeployDisabled(File file) { final File parent = file.getParentFile(); final String name = file.getName(); return new File(parent, name + SKIP_DEPLOY).exists() || new File(parent, name + DO_DEPLOY).exists(); } private long getDeploymentTimestamp(File deploymentFile) { if (deploymentFile.isDirectory()) { // Scan for most recent file long latest = deploymentFile.lastModified(); for (File child : listDirectoryChildren(deploymentFile)) { long childTimestamp = getDeploymentTimestamp(child); if (childTimestamp > latest) { latest = childTimestamp; } } return latest; } else { return deploymentFile.lastModified(); } } private boolean isEEArchive(String fileName) { return ARCHIVE_PATTERN.matcher(fileName).matches(); } private boolean isXmlFile(String fileName) { return fileName.endsWith(".xml"); } private void removeExtraneousMarker(File child, final String fileName) { if (!child.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(fileName); } } /** * Handle incompletely copied or non-scannable auto-deploy content and then abort scan * * @return true if the scan should be aborted */ private ScanStatus handleAutoDeployFailures(ScanContext scanContext) { ScanStatus result = ScanStatus.PROCEED; boolean warnLogged = false; Set<File> noLongerIncomplete = new HashSet<File>(incompleteDeployments.keySet()); noLongerIncomplete.removeAll(scanContext.incompleteFiles.keySet()); int oldIncompleteCount = incompleteDeployments.size(); incompleteDeployments.keySet().retainAll(scanContext.incompleteFiles.keySet()); if (scanContext.incompleteFiles.size() > 0) { result = ScanStatus.RETRY; // If user dealt with some incomplete stuff but others remain, log everything again boolean logAll = incompleteDeployments.size() != oldIncompleteCount; long now = System.currentTimeMillis(); for (Map.Entry<File, IncompleteDeploymentStatus> entry : scanContext.incompleteFiles.entrySet()) { File incompleteFile = entry.getKey(); String deploymentName = incompleteFile.getName(); IncompleteDeploymentStatus status = incompleteDeployments.get(incompleteFile); if (status == null || status.size < entry.getValue().size) { status = entry.getValue(); } if (now - status.timestamp > maxNoProgress) { if (!status.warned) { // Treat no progress for an extended period as a failed deployment String suffix = deployed.containsKey(deploymentName) ? DeploymentScannerLogger.ROOT_LOGGER.previousContentDeployed() : ""; String msg = DeploymentScannerLogger.ROOT_LOGGER.deploymentContentIncomplete(incompleteFile, suffix); writeFailedMarker(incompleteFile, msg, status.timestamp); ROOT_LOGGER.error(msg); status.warned = true; warnLogged = true; result = ScanStatus.ABORT; } // Clean up any .pending file new File(incompleteFile.getParentFile(), deploymentName + PENDING).delete(); } else { boolean newIncomplete = incompleteDeployments.put(incompleteFile, status) == null; if (newIncomplete || logAll) { ROOT_LOGGER.incompleteContent(entry.getKey().getPath()); } if (newIncomplete) { File pending = new File(incompleteFile.getParentFile(), deploymentName + PENDING); createMarkerFile(pending, deploymentName); } } } } // Clean out any old "pending" files for (File complete : noLongerIncomplete) { File pending = new File(complete.getParentFile(), complete.getName() + PENDING); removeExtraneousMarker(pending, pending.getName()); } int oldNonScannableCount = nonscannableLogged.size(); nonscannableLogged.retainAll(scanContext.nonscannable.keySet()); if (scanContext.nonscannable.size() > 0) { result = (result == ScanStatus.PROCEED ? ScanStatus.RETRY : result); // If user dealt with some nonscannable stuff but others remain, log everything again boolean logAll = nonscannableLogged.size() != oldNonScannableCount; for (Map.Entry<File, NonScannableStatus> entry : scanContext.nonscannable.entrySet()) { File nonScannable = entry.getKey(); String fileName = nonScannable.getName(); if (nonscannableLogged.add(nonScannable) || logAll) { NonScannableStatus nonScannableStatus = entry.getValue(); NonScannableZipException e = nonScannableStatus.exception; String msg = DeploymentScannerLogger.ROOT_LOGGER.unsafeAutoDeploy2(e.getLocalizedMessage(), fileName, DO_DEPLOY); writeFailedMarker(nonScannable, msg, nonScannableStatus.timestamp); ROOT_LOGGER.error(msg); warnLogged = true; result = ScanStatus.ABORT; } } } if (warnLogged) { Set<String> allProblems = new HashSet<String>(); for (File f : scanContext.nonscannable.keySet()) { allProblems.add(f.getName()); } for (File f : scanContext.incompleteFiles.keySet()) { allProblems.add(f.getName()); } ROOT_LOGGER.unsafeAutoDeploy(DO_DEPLOY, SKIP_DEPLOY, allProblems); } return result; } private synchronized void startScan() { if (scanEnabled) { if (scanInterval > 0) { scanTask = scheduledExecutor.scheduleWithFixedDelay(scanRunnable, 0, scanInterval, TimeUnit.MILLISECONDS); } else { scanTask = scheduledExecutor.schedule(scanRunnable, scanInterval, TimeUnit.MILLISECONDS); } } } /** * Invoke with the object monitor held */ private void cancelScan() { if (rescanIncompleteTask != null) { rescanIncompleteTask.cancel(true); rescanIncompleteTask = null; } if (rescanUndeployTask != null) { rescanUndeployTask.cancel(true); rescanUndeployTask = null; } if (scanTask != null) { scanTask.cancel(true); scanTask = null; } } private ModelNode getCompositeUpdate(final List<ModelNode> updates) { final ModelNode op = Util.getEmptyOperation(COMPOSITE, new ModelNode()); final ModelNode steps = op.get(STEPS); for (ModelNode update : updates) { steps.add(update); } op.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).set(rollbackOnRuntimeFailure); return op; } private ModelNode getCompositeUpdate(final ModelNode... updates) { final ModelNode op = Util.getEmptyOperation(COMPOSITE, new ModelNode()); final ModelNode steps = op.get(STEPS); for (ModelNode update : updates) { steps.add(update); } op.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).set(rollbackOnRuntimeFailure); return op; } private void safeClose(final Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException ignored) { } } } private void createMarkerFile(final File marker, String deploymentName) { FileOutputStream fos = null; try { // marker.createNewFile(); - Don't create before the write as there is a potential race condition where // the file is deleted between the two calls. fos = new FileOutputStream(marker); fos.write(deploymentName.getBytes(StandardCharsets.UTF_8)); } catch (IOException io) { ROOT_LOGGER.errorWritingDeploymentMarker(io, marker.getAbsolutePath()); } finally { safeClose(fos); } } private void writeFailedMarker(final File deploymentFile, final String failureDescription, long failureTimestamp) { final File failedMarker = new File(deploymentFile.getParent(), deploymentFile.getName() + FAILED_DEPLOY); final File deployMarker = new File(deploymentFile.getParent(), deploymentFile.getName() + DO_DEPLOY); if (deployMarker.exists() && !deployMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(deployMarker); } final File deployedMarker = new File(deploymentFile.getParent(), deploymentFile.getName() + DEPLOYED); if (deployedMarker.exists() && !deployedMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(deployedMarker); } final File undeployedMarker = new File(deploymentFile.getParent(), deploymentFile.getName() + UNDEPLOYED); if (undeployedMarker.exists() && !undeployedMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(undeployedMarker); } FileOutputStream fos = null; try { // failedMarker.createNewFile(); fos = new FileOutputStream(failedMarker); fos.write(failureDescription.getBytes(StandardCharsets.UTF_8)); } catch (IOException io) { ROOT_LOGGER.errorWritingDeploymentMarker(io, failedMarker.getAbsolutePath()); } finally { safeClose(fos); } } private static List<File> listDirectoryChildren(File directory) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory.toPath())) { final List<File> result = new ArrayList<>(); stream.forEach(entry -> result.add(entry.toFile())); return result; } catch (SecurityException | IOException ex) { throw DeploymentScannerLogger.ROOT_LOGGER.cannotListDirectoryFiles(ex, directory); } } private static List<File> listDirectoryChildren(File directory, DirectoryStream.Filter<Path> filter) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory.toPath(), filter)) { final List<File> result = new ArrayList<>(); stream.forEach(entry -> result.add(entry.toFile())); return result; } catch (SecurityException | IOException ex) { throw DeploymentScannerLogger.ROOT_LOGGER.cannotListDirectoryFiles(ex, directory); } } private abstract class ScannerTask { protected final String deploymentName; protected final String parent; private final String inProgressMarkerSuffix; private ScannerTask(final String deploymentName, final File parent, final String inProgressMarkerSuffix) { this.deploymentName = deploymentName; this.parent = parent.getAbsolutePath(); this.inProgressMarkerSuffix = inProgressMarkerSuffix; File marker = new File(parent, deploymentName + PENDING); if (!marker.exists()) { createMarkerFile(marker, deploymentName); } } protected void recordInProgress() { File marker = new File(parent, deploymentName + inProgressMarkerSuffix); createMarkerFile(marker, deploymentName); deleteUndeployedMarker(); deletePendingMarker(); } protected abstract ModelNode getUpdate(); protected abstract void handleSuccessResult(); protected abstract void handleFailureResult(final ModelNode result); protected void deletePendingMarker() { final File pendingMarker = new File(parent, deploymentName + PENDING); if (pendingMarker.exists() && !pendingMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(pendingMarker); } } protected void deleteUndeployedMarker() { final File undeployedMarker = new File(parent, deploymentName + UNDEPLOYED); if (undeployedMarker.exists() && !undeployedMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(undeployedMarker); } } protected void deleteDeployedMarker() { final File deployedMarker = new File(parent, deploymentName + DEPLOYED); if (deployedMarker.exists() && !deployedMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(deployedMarker); } } protected void removeInProgressMarker() { File marker = new File(new File(parent), deploymentName + inProgressMarkerSuffix); if (marker.exists() && !marker.delete()) { ROOT_LOGGER.cannotDeleteDeploymentProgressMarker(marker); } } } private abstract class ContentAddingTask extends ScannerTask { private final String path; private final boolean archive; protected final File deploymentFile; protected final long doDeployTimestamp; protected ContentAddingTask(final String path, final boolean archive, final String deploymentName, final File deploymentFile, long markerTimestamp) { super(deploymentName, deploymentFile.getParentFile(), DEPLOYING); this.path = path; this.archive = archive; this.deploymentFile = deploymentFile; this.doDeployTimestamp = markerTimestamp; } protected ModelNode createContent() { final ModelNode content = new ModelNode(); final ModelNode contentItem = content.get(0); if (archive) { try { contentItem.get(URL).set(deploymentFile.toURI().toURL().toString()); } catch (MalformedURLException ex) { } } if (!contentItem.hasDefined(URL)) { contentItem.get(ARCHIVE).set(archive); contentItem.get(PATH).set(path); if (relativeTo != null) { contentItem.get(RELATIVE_TO).set(relativeTo); } } return content; } @Override protected void handleSuccessResult() { final File parentFolder = new File(parent); final File doDeployMarker = new File(parentFolder, deploymentFile.getName() + DO_DEPLOY); if (doDeployMarker.exists() && !doDeployMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(doDeployMarker.getAbsolutePath()); } // Remove any previous failure marker final File failedMarker = new File(deploymentFile.getParent(), deploymentFile.getName() + FAILED_DEPLOY); if (failedMarker.exists() && !failedMarker.delete()) { ROOT_LOGGER.cannotRemoveDeploymentMarker(failedMarker); } final File deployedMarker = new File(parent, deploymentFile.getName() + DEPLOYED); createMarkerFile(deployedMarker, deploymentName); deployedMarker.setLastModified(doDeployTimestamp); if (deployed.containsKey(deploymentName)) { deployed.remove(deploymentName); } deployed.put(deploymentName, new DeploymentMarker(doDeployTimestamp, archive, parentFolder)); // Remove the in-progress marker - save this until the deployment is really complete. removeInProgressMarker(); } } private final class DeployTask extends ContentAddingTask { private DeployTask(final String path, final boolean archive, final String deploymentName, final File deploymentFile, long markerTimestamp) { super(path, archive, deploymentName, deploymentFile, markerTimestamp); } @Override protected ModelNode getUpdate() { final ModelNode address = new ModelNode().add(DEPLOYMENT, deploymentName); final ModelNode addOp = Util.getEmptyOperation(DeploymentAddHandler.OPERATION_NAME, address); addOp.get(CONTENT).set(createContent()); addOp.get(PERSISTENT).set(false); addOp.get(OWNER).set(resourceAddress); final ModelNode deployOp = Util.getEmptyOperation(DeploymentDeployHandler.OPERATION_NAME, address); deployOp.get(OWNER).set(resourceAddress); return getCompositeUpdate(addOp, deployOp); } @Override protected void handleFailureResult(final ModelNode result) { // Remove the in-progress marker removeInProgressMarker(); writeFailedMarker(deploymentFile, result.get(FAILURE_DESCRIPTION).toString(), doDeployTimestamp); } } private final class ReplaceTask extends ContentAddingTask { private ReplaceTask(final String path, final boolean archive, String deploymentName, File deploymentFile, long markerTimestamp) { super(path, archive, deploymentName, deploymentFile, markerTimestamp); } @Override protected ModelNode getUpdate() { final ModelNode replaceOp = Util.getEmptyOperation(DeploymentFullReplaceHandler.OPERATION_NAME, new ModelNode()); replaceOp.get(NAME).set(deploymentName); replaceOp.get(CONTENT).set(createContent()); replaceOp.get(PERSISTENT).set(false); replaceOp.get(OWNER).set(resourceAddress); replaceOp.get(ENABLED).set(true); return replaceOp; } @Override protected void handleFailureResult(ModelNode result) { // Remove the in-progress marker removeInProgressMarker(); writeFailedMarker(deploymentFile, result.get(FAILURE_DESCRIPTION).toString(), doDeployTimestamp); } } private final class RedeployTask extends ScannerTask { private final long markerLastModified; private final boolean archive; private RedeployTask(final String deploymentName, final long markerLastModified, final File parent, boolean archive) { super(deploymentName, parent, DEPLOYING); this.markerLastModified = markerLastModified; this.archive = archive; } @Override protected ModelNode getUpdate() { final ModelNode address = new ModelNode().add(DEPLOYMENT, deploymentName); final ModelNode redployOp = Util.getEmptyOperation(DeploymentRedeployHandler.OPERATION_NAME, address); redployOp.get(OWNER).set(resourceAddress); return redployOp; } @Override protected void handleSuccessResult() { // Remove the in-progress marker removeInProgressMarker(); deployed.remove(deploymentName); deployed.put(deploymentName, new DeploymentMarker(markerLastModified, archive, new File(parent))); } @Override protected void handleFailureResult(ModelNode result) { // Remove the in-progress marker removeInProgressMarker(); writeFailedMarker(new File(parent, deploymentName), result.get(FAILURE_DESCRIPTION).toString(), markerLastModified); } } private final class UndeployTask extends ScannerTask { private final long scanStartTime; private boolean forcedUndeploy; private UndeployTask(final String deploymentName, final File parent, final long scanStartTime, boolean forcedUndeploy) { super(deploymentName, parent, UNDEPLOYING); this.scanStartTime = scanStartTime; this.forcedUndeploy = forcedUndeploy; } @Override protected ModelNode getUpdate() { final ModelNode address = new ModelNode().add(DEPLOYMENT, deploymentName); final ModelNode undeployOp = Util.getEmptyOperation(DeploymentUndeployHandler.OPERATION_NAME, address); undeployOp.get(OWNER).set(resourceAddress); final ModelNode removeOp = Util.getEmptyOperation(DeploymentRemoveHandler.OPERATION_NAME, address); return getCompositeUpdate(undeployOp, removeOp); } @Override protected void handleSuccessResult() { // Remove the in-progress marker and any .deployed marker removeInProgressMarker(); if (!forcedUndeploy) { deleteDeployedMarker(); final File undeployedMarker = new File(parent, deploymentName + UNDEPLOYED); createMarkerFile(undeployedMarker, deploymentName); undeployedMarker.setLastModified(scanStartTime); } deployed.remove(deploymentName); noticeLogged.remove(deploymentName); } @Override protected void handleFailureResult(ModelNode result) { // Remove the in-progress marker removeInProgressMarker(); if (!forcedUndeploy) { writeFailedMarker(new File(parent, deploymentName), result.get(FAILURE_DESCRIPTION).toString(), scanStartTime); } } } private class DeploymentMarker { private final long lastModified; private final boolean archive; private final File parentFolder; private DeploymentMarker(final long lastModified, boolean archive, File parentFolder) { this.lastModified = lastModified; this.archive = archive; this.parentFolder = parentFolder; } } private class ScanContext { /** * Existing deployments */ private final Map<String, Boolean> registeredDeployments; /** * Existing persistent deployments */ private final Set<String> persistentDeployments; /** * Tasks generated by the scan */ private final List<ScannerTask> scannerTasks = new ArrayList<ScannerTask>(); /** * Files to undeploy at the end of the scan */ private final Map<String, DeploymentMarker> toRemove = new HashMap<String, DeploymentMarker>(deployed); /** * Marker files with no corresponding content */ private final HashSet<String> ignoredMissingDeployments = new HashSet<String>(); /** * Partially copied files detected by the scan */ private Map<File, IncompleteDeploymentStatus> incompleteFiles = new HashMap<File, IncompleteDeploymentStatus>(); /** * Non-auto-deployable files detected by the scan without an appropriate marker */ private final HashSet<String> nonDeployable = new HashSet<String>(); /** * WEB-INF and META-INF dirs not enclosed by a deployment */ private final HashSet<String> illegalDir = new HashSet<String>(); /** * Exploded deployment content removed without first removing the .deployed marker */ private final HashSet<String> prematureExplodedDeletions = new HashSet<String>(); /** * Deployments to attempt in next firstScan during boot */ private final HashSet<String> firstScanDeployments = new HashSet<String>(); /** * Auto-deployable files detected by the scan where ZipScanner threw a NonScannableZipException */ private final Map<File, NonScannableStatus> nonscannable = new HashMap<File, NonScannableStatus>(); /** * Timestamp when the scan started */ private final long scanStartTime = System.currentTimeMillis(); private ScanContext(final DeploymentOperations deploymentOperations) { registeredDeployments = deploymentOperations.getDeploymentsStatus(); persistentDeployments = deploymentOperations.getUnrelatedDeployments(resourceAddress); } } private static class IncompleteDeploymentStatus { private final long timestamp; private final long size; private boolean warned; IncompleteDeploymentStatus(final File file, final long timestamp) { this.size = file.length(); this.timestamp = timestamp; } } private static class NonScannableStatus { private final long timestamp; private final NonScannableZipException exception; public NonScannableStatus(NonScannableZipException exception, long timestamp) { this.exception = exception; this.timestamp = timestamp; } } /** * Possible overall scan behaviors following return from handling auto-deploy failures */ private enum ScanStatus { ABORT, RETRY, PROCEED } }