/* * 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.synapse.deployers; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMException; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.axiom.om.util.StAXUtils; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.deployment.AbstractDeployer; import org.apache.axis2.deployment.DeploymentException; import org.apache.axis2.deployment.repository.util.DeploymentFileData; import org.apache.axis2.description.Parameter; import org.apache.axis2.util.XMLPrettyPrinter; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.ServerConfigurationInformation; import org.apache.synapse.ServerContextInformation; import org.apache.synapse.ServerState; import org.apache.synapse.SynapseConstants; import org.apache.synapse.config.SynapseConfiguration; import org.apache.synapse.core.SynapseEnvironment; import javax.xml.stream.XMLStreamException; import java.io.*; import java.util.Properties; /** * Implements the generic logic for the synapse artifact deployment and provide a deployment * framework for the synapse.</p> * * <p>Any synapse artifact which requires the hot deployment or hot update features should extend * this and just needs to concentrate on the deployment logic. By default setting the file * extension and directory dynamically is not supported. * * @see org.apache.axis2.deployment.Deployer */ public abstract class AbstractSynapseArtifactDeployer extends AbstractDeployer { private static final Log log = LogFactory.getLog(AbstractSynapseArtifactDeployer.class); protected Log deployerLog; protected ConfigurationContext cfgCtx; protected String customLogContent; protected AbstractSynapseArtifactDeployer() { deployerLog = LogFactory.getLog(this.getClass()); } /** * Initializes the Synapse artifact deployment * * @param configCtx Axis2 ConfigurationContext */ public void init(ConfigurationContext configCtx) { this.cfgCtx = configCtx; } private boolean isHotDeploymentEnabled() { try { return getSynapseConfiguration().isAllowHotUpdate(); } catch (DeploymentException e) { log.warn("Error while retrieving the SynapseConfiguration", e); return false; } } /** * This method is called by the axis2 deployment framework and it performs a synapse artifact * specific yet common across all the artifacts, set of tasks and delegate the actual deployment * to the respective artifact deployers. * * @param deploymentFileData file to be used for the deployment * @throws org.apache.axis2.deployment.DeploymentException in-case of an error in deploying the file * * @see org.apache.synapse.deployers.AbstractSynapseArtifactDeployer#deploySynapseArtifact(org.apache.axiom.om.OMElement, * String,java.util.Properties) */ public void deploy(DeploymentFileData deploymentFileData) throws DeploymentException { // CustomLogSetter.getInstance().setLogAppender(customLogContent); if (!isHotDeploymentEnabled()) { if (log.isDebugEnabled()) { log.debug("Hot deployment has been suspended - Ignoring"); } return; } String filename = SynapseArtifactDeploymentStore.getNormalizedAbsolutePath( deploymentFileData.getAbsolutePath()); if (log.isDebugEnabled()) { log.debug("Deployment of the synapse artifact from file : " + filename + " : STARTED"); } if (getServerContextInformation().getServerState() != ServerState.STARTED) { // synapse server has not yet being started if (log.isDebugEnabled()) { log.debug("Skipped the artifact deployment (since the Synapse " + "server doesn't seem to be started yet), from file : " + deploymentFileData.getAbsolutePath()); } return; } SynapseArtifactDeploymentStore deploymentStore = getSynapseConfiguration().getArtifactDeploymentStore(); // check whether this is triggered by a restore, if it is a restore we do not want to // deploy it again if (deploymentStore.isRestoredFile(filename)) { if (log.isDebugEnabled()) { log.debug("Restored artifact detected with filename : " + filename); } // only one deployment trigger can happen after a restore and hence remove it from // restoredFiles at the first hit, allowing the further deployments/updates to take // place as usual deploymentStore.removeRestoredFile(filename); return; } try { InputStream in = FileUtils.openInputStream(new File(filename)); try { // construct the xml element from the file, it has to be XML, // since all synapse artifacts are XML based OMElement element = new StAXOMBuilder( StAXUtils.createXMLStreamReader(in)).getDocumentElement(); Properties properties = new Properties(); properties.put(SynapseConstants.CLASS_MEDIATOR_LOADERS, deploymentStore.getClassMediatorClassLoaders()); properties.put(SynapseConstants.RESOLVE_ROOT, getSynapseEnvironment() .getServerContextInformation() .getServerConfigurationInformation().getResolveRoot()); String artifactName = null; if (deploymentStore.isUpdatingArtifact(filename)) { if (log.isDebugEnabled()) { log.debug("Updating artifact detected with filename : " + filename); } // this is an hot-update case String existingArtifactName = deploymentStore.getUpdatingArtifactWithFileName(filename); deploymentStore.removeUpdatingArtifact(filename); try { artifactName = updateSynapseArtifact( element, filename, existingArtifactName, properties); } catch (SynapseArtifactDeploymentException | OMException ex) { log.error("Update of the Synapse Artifact from file : " + filename + " : Failed!", ex); log.info("The updated file has been backed up into : " + backupFile(deploymentFileData.getFile())); log.info("Restoring the existing artifact into the file : " + filename); restoreSynapseArtifact(existingArtifactName); deploymentStore.addArtifact(filename, existingArtifactName); artifactName = existingArtifactName; throw new DeploymentException(ex); } } else { // new artifact hot-deployment case try { // When someone deploys either main or fault sequence what actually happens is they simply // update the existing sequences. if (filename.matches(".*/main-\\d+\\.\\d+\\.\\d+\\.xml")) { artifactName = updateDefaultSequence(filename, element, properties, deploymentStore.getMainSeqLstUpdatedFile(), deploymentStore); String mainSeqFileName = filename.substring(filename.lastIndexOf(File.separator) + 1); deploymentStore.setMainSeqLstUpdatedFile(mainSeqFileName); } else if (filename.matches(".*/fault-\\d+\\.\\d+\\.\\d+\\.xml")) { artifactName = updateDefaultSequence(filename, element, properties, deploymentStore.getFaultSeqLstUpdatedFile(), deploymentStore); String faultSeqFileName = filename.substring(filename.lastIndexOf(File.separator) + 1); deploymentStore.setFaultSeqLstUpdatedFile(faultSeqFileName); } else { artifactName = deploySynapseArtifact(element, filename, properties); } } catch (SynapseArtifactDeploymentException sade) { log.error("Deployment of the Synapse Artifact from file : " + filename + " : Failed!", sade); log.info("The file has been backed up into : " + backupFile(deploymentFileData.getFile())); throw new DeploymentException(sade); } } if (artifactName != null) { deploymentStore.addArtifact(filename, artifactName); } } finally { in.close(); } } catch (IOException ex) { handleDeploymentError("Deployment of synapse artifact failed. Error reading " + filename + " : " + ex.getMessage(), ex, filename); throw new DeploymentException(ex); } catch (XMLStreamException ex) { handleDeploymentError("Deployment of synapse artifact failed. Error parsing " + filename + " : " + ex.getMessage(), ex, filename); throw new DeploymentException(ex); } catch (OMException ex) { handleDeploymentError("Deployment of synapse artifact failed. Error parsing " + filename + " : " + ex.getMessage(), ex, filename); throw new DeploymentException(ex); } if (log.isDebugEnabled()) { log.debug("Deployment of the synapse artifact from file : " + filename + " : COMPLETED"); } } /** * This is the method called by the axis2 framework for undeployment of the artifacts. As in * the deploy case this performs some common tasks across all the synapse artifacts and fall * back to the artifact specific logic of undeployment. * * @param fileName file describing the artifact to be undeployed * @throws org.apache.axis2.deployment.DeploymentException in case of an error in undeployment * * @see AbstractSynapseArtifactDeployer#undeploySynapseArtifact( * String) */ public void undeploy(String fileName) throws DeploymentException { if (!isHotDeploymentEnabled()) { if (log.isDebugEnabled()) { log.debug("Hot deployment has been suspended - Ignoring"); } return; } fileName = SynapseArtifactDeploymentStore.getNormalizedAbsolutePath(fileName); if (log.isDebugEnabled()) { log.debug("Undeployment of the synapse artifact from file : " + fileName + " : STARTED"); } SynapseArtifactDeploymentStore deploymentStore = getSynapseConfiguration().getArtifactDeploymentStore(); // We want to eliminate the undeployment when we are backing up these files if (deploymentStore.isBackedUpArtifact(fileName)) { if (log.isDebugEnabled()) { log.debug("BackedUp artifact detected with filename : " + fileName); } // only one undeployment trigger can happen after a backup and hence remove it from // backedUpFiles at the first hit, allowing the further undeployment ops to take place // as usual deploymentStore.removeBackedUpArtifact(fileName); return; } if (deploymentStore.containsFileName(fileName)) { File undeployingFile = new File(fileName); // axis2 treats Hot-Update as (Undeployment + deployment), where synapse needs to // differentiate the Hot-Update from the above two, since it needs some validations for // a real undeployment. Also this makes sure a zero downtime of the synapse artifacts // which are being Hot-deployed if (undeployingFile.exists()) { if (log.isDebugEnabled()) { log.debug("Marking artifact as updating from file : " + fileName); } // if the file exists, which means it has been updated and is a Hot-Update case if (!deploymentStore.isRestoredFile(fileName)) { deploymentStore.addUpdatingArtifact( fileName, deploymentStore.getArtifactNameForFile(fileName)); deploymentStore.removeArtifactWithFileName(fileName); } } else { // if the file doesn't exists then it is an actual undeployment String artifactName = deploymentStore.getArtifactNameForFile(fileName); try { // It is not possible to un-deploy either main or fault sequence. Instead when someone // un-deploy one of these updated sequences, It falls back to default sequence. if (fileName.matches(".*main-\\d+\\.\\d+\\.\\d+\\.xml")) { String mainSeqFileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1); // TODO - Improve by adding proper version control if (mainSeqFileName.equals(deploymentStore.getMainSeqLstUpdatedFile())) { resetDefaultSequence(SynapseConstants.MAIN_SEQUENCE_XML, fileName, deploymentStore); deploymentStore.setMainSeqLstUpdatedFile(SynapseConstants.MAIN_SEQUENCE_XML); } } else if (fileName.matches(".*fault-\\d+\\.\\d+\\.\\d+\\.xml")) { String faultSeqFileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1); if (faultSeqFileName.equals(deploymentStore.getFaultSeqLstUpdatedFile())) { resetDefaultSequence(SynapseConstants.FAULT_SEQUENCE_XML, fileName, deploymentStore); deploymentStore.setFaultSeqLstUpdatedFile(SynapseConstants.FAULT_SEQUENCE_XML); } } else { undeploySynapseArtifact(artifactName); deploymentStore.removeArtifactWithFileName(fileName); } } catch (SynapseArtifactDeploymentException sade) { log.error("Unable to undeploy the artifact from file : " + fileName, sade); log.info("Restoring the artifact into the file : " + fileName); restoreSynapseArtifact(artifactName); } catch (IOException ioe) { log.error("Unable to undeploy the artifact from file : " + fileName, ioe); log.info("Restoring the artifact into the file : " + fileName); restoreSynapseArtifact(artifactName); } catch (XMLStreamException xmlse) { log.error("Unable to undeploy the artifact from file : " + fileName, xmlse); log.info("Restoring the artifact into the file : " + fileName); restoreSynapseArtifact(artifactName); } } } else { String msg = "Artifact representing the filename " + fileName + " is not deployed on Synapse"; if (log.isDebugEnabled()) { log.debug(msg); } //throw new DeploymentException(msg); } if (log.isDebugEnabled()) { log.debug("UnDeployment of the synapse artifact from file : " + fileName + " : COMPLETED"); } } // We do not support dynamically setting the directory nor the extension public void setDirectory(String directory) {} public void setExtension(String extension) {} /** * All synapse artifact deployers MUST implement this method and it handles artifact specific * deployment tasks of those artifacts. * * @param artifactConfig built element representing the artifact to be deployed loaded * from the file * @param fileName file name from which this artifact is being loaded * @param properties Properties associated with the artifact * @return String artifact name created by the deployment task * * @see AbstractSynapseArtifactDeployer#deploy( * org.apache.axis2.deployment.repository.util.DeploymentFileData) */ public abstract String deploySynapseArtifact(OMElement artifactConfig, String fileName, Properties properties); /** * All synapse artifact deployers MUST implement this method and it handles artifact specific * update tasks of those artifacts. * * @param artifactConfig built element representing the artifact to be deployed loaded * from the file * @param fileName file name from which this artifact is being loaded * @param existingArtifactName name of the artifact that was being deployed using * the updated file * @param properties bag of properties with the additional information * @return String artifact name created by the update task */ public abstract String updateSynapseArtifact(OMElement artifactConfig, String fileName, String existingArtifactName, Properties properties); /** * All synapse artifact deployers MUST implement this method and it handles artifact specific * undeployment tasks of those artifacts. * * @param artifactName name of the artifact to be undeployed * * @see AbstractSynapseArtifactDeployer#undeploy(String) */ public abstract void undeploySynapseArtifact(String artifactName); /** * All synapse artifact deployers MUST implement this method and it handles artifact specific * restore tasks of those artifacts upon a failure of an update or undeployment. * * @param artifactName name of the artifact to be restored */ public abstract void restoreSynapseArtifact(String artifactName); protected SynapseConfiguration getSynapseConfiguration() throws DeploymentException { Parameter synCfgParam = cfgCtx.getAxisConfiguration().getParameter(SynapseConstants.SYNAPSE_CONFIG); if (synCfgParam == null) { throw new DeploymentException("SynapseConfiguration not found. " + "Are you sure that you are running Synapse?"); } return (SynapseConfiguration) synCfgParam.getValue(); } protected SynapseEnvironment getSynapseEnvironment() throws DeploymentException { Parameter synCfgParam = cfgCtx.getAxisConfiguration().getParameter(SynapseConstants.SYNAPSE_ENV); if (synCfgParam == null) { throw new DeploymentException("SynapseEnvironment not found. " + "Are you sure that you are running Synapse?"); } return (SynapseEnvironment) synCfgParam.getValue(); } protected ServerConfigurationInformation getServerConfigurationInformation() throws DeploymentException { Parameter serverCfgParam = cfgCtx.getAxisConfiguration().getParameter( SynapseConstants.SYNAPSE_SERVER_CONFIG_INFO); if (serverCfgParam == null) { throw new DeploymentException("SynapseConfigurationInformation not found. " + "Are you sure that you are running Synapse?"); } return (ServerConfigurationInformation) serverCfgParam.getValue(); } protected ServerContextInformation getServerContextInformation() throws DeploymentException { Parameter serverCtxParam = cfgCtx.getAxisConfiguration().getParameter(SynapseConstants.SYNAPSE_SERVER_CTX_INFO); if (serverCtxParam == null) { throw new DeploymentException("ServerContextInformation not found. " + "Are you sure that you are running Synapse?"); } return (ServerContextInformation) serverCtxParam.getValue(); } protected void writeToFile(OMElement content, String fileName) throws Exception { // this is not good, but I couldn't think of a better design :-( SynapseArtifactDeploymentStore deploymentStore = getSynapseConfiguration().getArtifactDeploymentStore(); deploymentStore.addRestoredArtifact(fileName); OutputStream out = FileUtils.openOutputStream(new File(fileName)); XMLPrettyPrinter.prettify(content, out); out.flush(); out.close(); } protected void waitForCompletion() { long timeout = 2000L; Parameter param = cfgCtx.getAxisConfiguration().getParameter("hotupdate.timeout"); if (param != null && param.getValue() != null) { timeout = Long.parseLong(param.getValue().toString()); } try { Thread.sleep(timeout); } catch (InterruptedException ignored) { } } protected void handleSynapseArtifactDeploymentError(String msg) { deployerLog.error(msg); throw new SynapseArtifactDeploymentException(msg); } protected void handleSynapseArtifactDeploymentError(String msg, Exception e) { deployerLog.error(msg, e); throw new SynapseArtifactDeploymentException(msg, e); } private void handleDeploymentError(String msg, Exception e, String fileName) throws DeploymentException { fileName = SynapseArtifactDeploymentStore.getNormalizedAbsolutePath(fileName); log.error(msg, e); SynapseArtifactDeploymentStore deploymentStore = getSynapseConfiguration().getArtifactDeploymentStore(); if (deploymentStore.isUpdatingArtifact(fileName)) { backupFile(new File(fileName)); log.info("Restoring the existing artifact into the file : " + fileName); restoreSynapseArtifact(deploymentStore.getUpdatingArtifactWithFileName(fileName)); deploymentStore.addArtifact( fileName, deploymentStore.getUpdatingArtifactWithFileName(fileName)); deploymentStore.removeUpdatingArtifact(fileName); } } private String backupFile(File file) throws DeploymentException { //TODO: WSO2 Specific hack to prevent creating backup files in worker nodes String isWorker = System.getProperty("workerNode"); if (isWorker!=null) { return "NO_BACKUP_ON_WORKER.INFO"; } String filePath = SynapseArtifactDeploymentStore.getNormalizedAbsolutePath( file.getAbsolutePath()); SynapseArtifactDeploymentStore deploymentStore = getSynapseConfiguration().getArtifactDeploymentStore(); deploymentStore.addBackedUpArtifact(filePath); String backupFilePath = filePath + ".back"; int backupIndex = 0; while (backupIndex >= 0) { if (new File(backupFilePath).exists()) { backupIndex++; backupFilePath = filePath + "." + backupIndex + ".back"; } else { backupIndex = -1; try { FileUtils.moveFile(file, new File(backupFilePath)); } catch (IOException e) { log.warn("Error while backing up the artifact: ", e); return "ERROR_WHILE_BACKING_UP_ARTIFACT"; } } } return backupFilePath; } private void resetDefaultSequence(String sequenceFileName, String fileName, SynapseArtifactDeploymentStore deploymentStore) throws IOException, XMLStreamException { String dirpath = fileName.substring(0, fileName.lastIndexOf(File.separator)); String seqOrgi = dirpath + File.separator + sequenceFileName; InputStream in = FileUtils.openInputStream(new File(seqOrgi)); // construct the xml element from the file, it has to be XML, // since all synapse artifacts are XML based OMElement element = new StAXOMBuilder( StAXUtils.createXMLStreamReader(in)).getDocumentElement(); Properties properties = new Properties(); properties.put(SynapseConstants.RESOLVE_ROOT, getSynapseEnvironment() .getServerContextInformation() .getServerConfigurationInformation().getResolveRoot()); String existingArtifactName = deploymentStore.getArtifactNameForFile(fileName); deploymentStore.removeArtifactWithFileName(fileName); String artifactName = updateSynapseArtifact( element, seqOrgi, existingArtifactName, properties); if (artifactName != null) { deploymentStore.addArtifact(seqOrgi, artifactName); } } private String updateDefaultSequence(String filename, OMElement element, Properties properties, String lstupdFile, SynapseArtifactDeploymentStore deploymentStore) { String derpath = filename.substring(0, filename.lastIndexOf(File.separator)); String seqFile = derpath + File.separator + lstupdFile; String existingArtifactName = deploymentStore.getArtifactNameForFile(seqFile); deploymentStore.removeArtifactWithFileName(seqFile); String artifactName = updateSynapseArtifact( element, filename, existingArtifactName, properties); return artifactName; } public void setCustomLog (String artifactContainerName, String tenantId) { if (artifactContainerName == null) { customLogContent = null; return; } customLogContent = "[ Deployed From Artifact Container: " + artifactContainerName + " ] "; } }