mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
30270: ALF-9492 Can now update task properties through the Workflow JavaScript API. ALF-10087 Fixed failing Multi-tenancy tests. 30306: Fixed ALF-7384: Xam: XamArchiveCleaner can delete files before retention period has passed - Keep the NOW upper limit to the query - Add a double-check using Java code to precisely check (down to the ms) that the retention time has passed 30308: Merged V3.4 to V3.4-BUG-FIX 30307: ALF-10186: Fix up all code that expects it can cast a WebScriptRequest directly into a WebScriptServletRequest 30310: ALF-5830 show_audit.ftl template doesn't work any more - Changes to support new audit api - Works with high level content auditing 30337: Merge DEV to V3.4-BUG-FIX 30160 : ALF-9257 - Renaming web project cause no trigger of the autodeploy process 30362: ALF-10227 Add space after ''{0}'' in text 30395: Removed deep svn:mergeinfo 30433: Merged DEV/TEMPORARY to V3.4-BUG-FIX 30424: ALF-8941: WCM: Virtual server setting lazyDeployExperimentalOnly ="true" (as per si3 fix) fails to load library under WEB-INF/lib directory (for HTTP filter). Walk up by hierarchy to the root and deploys a context if it wasn't deployed yet, through invocation of updateAllVirtualWebapps recursively. 30454: Fixed ALF-9158: Assignment of workflow task is not proper - out-of-the-box WCM workflow 30466: Merged DEV/TEMPORARY to V3.4-BUG-FIX 30460: ALF-9424 : Webform(XSD): xf:switch switch group does not work based on "complexContent with Base" element. Schema2XForms and SchemaUtil were modified to allow correctly process elements with namespace that differ from "http://www.w3.org/2001/XMLSchema" 30476: Merged DEV to V3.4-BUG-FIX 30474: ALF-10021: Get the last node in the resultset outside the timing loop to force prefetch. 30483: Fixed ALF-9417: (pdf2swf) Share preview is blank with some pdf files. 30514: ALF-240: Unfriendly error appears when trying to view details for created discussion for multilingual content Webdav URLs were not valid for non-ML or ML discussion items, but were only stopping the details page for the ML items from being shown. Utils.generateURL now returns null for such items. 30517: Fixed ALF-5526: Component-Generator for d:noteref and other "system" types always disabled => breaks extensibility 30519: FileFolderService moveFrom method was not supported by MLTranslationInterceptor 30527: ALF-240: added missing exception message. 30531: Fixed ALF-9599: Share forms do not allow edit of non-cm:content nodes 30541: ALF-9424: Missed change, causing compilation error 30552: Tests to accompany fix for ALF-240, ALF-10298: discussion topic webdav URLs causing error page in explorer. 30565: Fixed ALF-10336 "Drag and Drop item in Customize Site Dashboard - text of item reverts to default font style and size during drag operation" 30568: Fixed ALF-10342: ClassCastException on org.alfresco.repo.copy.AbstractCopyBehaviourCallback 30570: ALF-3332: (circa 2008) Pagination inks rewritten incorrectly by opensearch proxy 30574: ALF-9470: OwnableService cache not being updated for archived nodes - modified fix by Pavel 30579: ALF-5607: Cancelling of install. Some directories are not deleted from disk. - Fixed provided by Bitrock for Mac and Windows 30583: Merged HEAD to V3.4-BUG-FIX (Back ported 2 fixes for included resource handling when running unit tests) 28711: Avoid a NPE on certain kinds missing included resources, and instead give a helpful error along the lines of other include issues 28716: When loading JS includes with a full classpath reference, handle the Eclipse classloader behaving slightly differently on absolute paths compared to the JVM one 30648: Fixed ALF-10401: No simple way to disable auto-versioning behaviour - Added property: version.store.enableAutoVersioning=true - When set to 'false', the VersionableAspect will not respond to any events; even if the aspect is present, it will not create versions. 30657: Merged DEV/TEMPORARY to V3.4-BUG-FIX 30590: ALF-7105: pdfbox returns errors in the logs but one cannot understand what file is affected (PDFBox) Level log4j.logger.org.apache.pdfbox.pdmodel.font.PDCIDFont=fatal was introduced in log4j.properties. 30669: Fixes: ALF-6470 (Updates FR translation) 30686: ALF-1017: Fixed compilation error in backport 30696: Fix for ALF-8176 30708: ALF-10040: Added missing ReferenceCountingReadOnlyIndexReaderFactory wrapper to IndexInfo.getMainIndexReferenceCountingReadOnlyIndexReader() to make it consistent with IndexInfo.getMainIndexReferenceCountingReadOnlyIndexReader(String, Set<String>, boolean) and allow SingleFieldSelectors to make it through from LeafScorer to the path caches! Affects ALL Lucene queries that run OUTSIDE of a transaction. 30722: Fixed ALF-9465: Share: We can add category in Share, but the selected value will not be shown in U.I. 30724: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_09_05 to BRANCHES/DEV/V3.4-BUG-FIX: 30603: ALF-10165: Unexpected behaviour when title duplicated between web forms 30754: Fix for ALF-9899 Huge share site migration, add group to site and user access site related performance issue. - generic performance improvements for PATH queries ending "..../*" - specifically to improve listing calendar items for users in many share sites 30765: Fix for ALF-760 - import loses category association data 30779: Merged V3.4 to V3.4-BUG-FIX 30716: ALF-10452 It's impossible to edit existing user details - Change for ALF-371 did not handle the simplest case - no change of home folder location 30549: Fixes ALF-9534 - Location API git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30780 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2277 lines
83 KiB
Java
2277 lines
83 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.alfresco.repo.deploy;
|
|
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.Serializable;
|
|
import java.text.MessageFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.SortedMap;
|
|
import java.util.TreeSet;
|
|
import java.util.concurrent.BlockingQueue;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import org.alfresco.deployment.DeploymentReceiverService;
|
|
import org.alfresco.deployment.DeploymentReceiverTransport;
|
|
import org.alfresco.deployment.DeploymentToken;
|
|
import org.alfresco.deployment.DeploymentTransportOutputFilter;
|
|
import org.alfresco.deployment.FileDescriptor;
|
|
import org.alfresco.deployment.FileType;
|
|
import org.alfresco.model.WCMAppModel;
|
|
import org.alfresco.repo.action.ActionServiceRemote;
|
|
import org.alfresco.repo.avm.AVMNodeConverter;
|
|
import org.alfresco.repo.avm.AVMNodeService;
|
|
import org.alfresco.repo.avm.util.SimplePath;
|
|
import org.alfresco.repo.domain.PropertyValue;
|
|
import org.alfresco.repo.lock.JobLockService;
|
|
import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback;
|
|
import org.alfresco.repo.remote.AVMRemoteImpl;
|
|
import org.alfresco.repo.remote.AVMSyncServiceRemote;
|
|
import org.alfresco.repo.remote.ClientTicketHolder;
|
|
import org.alfresco.repo.remote.ClientTicketHolderThread;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.service.cmr.action.ActionService;
|
|
import org.alfresco.service.cmr.action.ActionServiceTransport;
|
|
import org.alfresco.service.cmr.avm.AVMException;
|
|
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
|
|
import org.alfresco.service.cmr.avm.AVMNotFoundException;
|
|
import org.alfresco.service.cmr.avm.AVMService;
|
|
import org.alfresco.service.cmr.avm.AVMStoreDescriptor;
|
|
import org.alfresco.service.cmr.avm.AVMWrongTypeException;
|
|
import org.alfresco.service.cmr.avm.deploy.DeploymentCallback;
|
|
import org.alfresco.service.cmr.avm.deploy.DeploymentEvent;
|
|
import org.alfresco.service.cmr.avm.deploy.DeploymentService;
|
|
import org.alfresco.service.cmr.avmsync.AVMDifference;
|
|
import org.alfresco.service.cmr.avmsync.AVMSyncService;
|
|
import org.alfresco.service.cmr.remote.AVMRemote;
|
|
import org.alfresco.service.cmr.remote.AVMRemoteTransport;
|
|
import org.alfresco.service.cmr.remote.AVMSyncServiceTransport;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.Path;
|
|
import org.alfresco.service.cmr.search.ResultSet;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.NameMatcher;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.PropertyCheck;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
|
|
|
|
/**
|
|
* Implementation of DeploymentService.
|
|
* @author britt
|
|
*/
|
|
public class DeploymentServiceImpl implements DeploymentService
|
|
{
|
|
private static Log fgLogger = LogFactory.getLog(DeploymentServiceImpl.class);
|
|
|
|
private NodeService nodeService;
|
|
private NamespacePrefixResolver namespacePrefixResolver;
|
|
private SearchService searchService;
|
|
|
|
/**
|
|
* The local AVMService Instance.
|
|
*/
|
|
private AVMService fAVMService;
|
|
|
|
private AVMNodeService fAVMNodeService;
|
|
|
|
/**
|
|
* The local Transaction Service Instance
|
|
*/
|
|
TransactionService trxService;
|
|
|
|
/**
|
|
* The jobLockService
|
|
*/
|
|
private JobLockService jobLockService;
|
|
|
|
/**
|
|
* The Ticket holder.
|
|
*/
|
|
private ClientTicketHolder fTicketHolder;
|
|
|
|
/**
|
|
* number of concurrent sending threads
|
|
*/
|
|
private int numberOfSendingThreads = 4;
|
|
|
|
/**
|
|
* Hold the deployment lock for 3600 seconds (1 hour)
|
|
* <p>
|
|
* This is how long we will wait for a business process to complete.
|
|
* And needs to be fairly long to allow transmission of of big files
|
|
* over high latency networks.
|
|
*/
|
|
private long targetLockTimeToLive = 3600000;
|
|
|
|
/**
|
|
* Refresh the lock every minute or so
|
|
* <p>
|
|
* This is how long we keep the lock for before nudging it. So if
|
|
* this node in the cluster is shut down during deployment then
|
|
* another node can take over.
|
|
*/
|
|
private long targetLockRefreshTime = 10000;
|
|
|
|
/**
|
|
* Retry for target lock every 1 second
|
|
*/
|
|
private long targetLockRetryWait = 1000;
|
|
|
|
/**
|
|
* Retry 10000 times before giving up, basically we
|
|
* never want to give up.
|
|
*/
|
|
private int targetLockRetryCount = 10001;
|
|
|
|
/**
|
|
* The size of the output buffers
|
|
*/
|
|
private int OUTPUT_BUFFER_SIZE = 20000;
|
|
|
|
private int outputBufferSize = OUTPUT_BUFFER_SIZE;
|
|
|
|
public void init()
|
|
{
|
|
PropertyCheck.mandatory(this, "jobLockService", jobLockService);
|
|
PropertyCheck.mandatory(this, "transactionService", trxService);
|
|
PropertyCheck.mandatory(this, "avmService", fAVMService);
|
|
PropertyCheck.mandatory(this, "avmNodeService", fAVMNodeService);
|
|
}
|
|
|
|
/**
|
|
* Default constructor.
|
|
*/
|
|
public DeploymentServiceImpl()
|
|
{
|
|
fTicketHolder = new ClientTicketHolderThread();
|
|
}
|
|
|
|
/**
|
|
* Setter.
|
|
* @param service The instance to set.
|
|
*/
|
|
public void setAvmService(AVMService service)
|
|
{
|
|
fAVMService = service;
|
|
}
|
|
|
|
/**
|
|
* Setter.
|
|
* @param trxService The instance to set.
|
|
*/
|
|
public void setTransactionService(TransactionService trxService)
|
|
{
|
|
this.trxService = trxService;
|
|
}
|
|
|
|
/**
|
|
* Setter.
|
|
* @param nodeService The instance to set.
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
/**
|
|
* Setter.
|
|
* @param namespacePrefixResolver The instance to set.
|
|
*/
|
|
public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver)
|
|
{
|
|
this.namespacePrefixResolver = namespacePrefixResolver;
|
|
}
|
|
|
|
/**
|
|
* Setter.
|
|
* @param searchService The instance to set.
|
|
*/
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
/*
|
|
* Deploy differences to an ASR
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.service.cmr.avm.deploy.DeploymentService#deployDifference(int, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
|
|
*/
|
|
public void deployDifference(int version,
|
|
String srcPath,
|
|
String hostName,
|
|
int port,
|
|
String userName,
|
|
String password,
|
|
String dstPath,
|
|
final NameMatcher matcher,
|
|
boolean createDst,
|
|
final boolean dontDelete,
|
|
final boolean dontDo,
|
|
final List<DeploymentCallback> callbacks)
|
|
{
|
|
final String storeName = srcPath.substring(0, srcPath.indexOf(":"));
|
|
|
|
/**
|
|
* Lock the cluster for the remote target
|
|
*/
|
|
String lockStr = hostName + "." + "asr." + storeName;
|
|
QName lockQName = QName.createQName("{http://www.alfresco.org/deploymentService/1.0}" + lockStr);
|
|
|
|
Lock lock = new Lock(lockQName);
|
|
lock.makeLock();
|
|
try
|
|
{
|
|
/**
|
|
* Got the lock - now do a deployment
|
|
*/
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("Deploying to Remote Alfresco at " + hostName);
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
RetryingTransactionHelper trn = trxService.getRetryingTransactionHelper();
|
|
|
|
fgLogger.debug("Connecting to remote AVM at " + hostName + ":" +port);
|
|
final AVMRemote remote = getRemote(hostName, port, userName, password);
|
|
if (version < 0)
|
|
{
|
|
/**
|
|
* If version is -1, Create a local snapshot to deploy
|
|
*/
|
|
fgLogger.debug("creating snapshot of local version");
|
|
|
|
|
|
RetryingTransactionCallback<Integer> localSnapshot = new RetryingTransactionCallback<Integer>()
|
|
{
|
|
public Integer execute() throws Throwable
|
|
{
|
|
int newVersion = fAVMService.createSnapshot(storeName, null, null).get(storeName);
|
|
return new Integer(newVersion);
|
|
}
|
|
};
|
|
version = trn.doInTransaction(localSnapshot, false, true).intValue();
|
|
fgLogger.debug("snapshot local created " + storeName + ", " + version);
|
|
}
|
|
|
|
{
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.START,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
dstPath);
|
|
processEvent(event, callbacks);
|
|
}
|
|
|
|
/*
|
|
* Create a snapshot on the destination server.
|
|
*/
|
|
boolean createdRoot = false;
|
|
String [] storePath = dstPath.split(":");
|
|
int snapshot = -1;
|
|
|
|
// Get the root of the deployment on the destination server.
|
|
AVMNodeDescriptor dstRoot = remote.lookup(-1, dstPath);
|
|
|
|
if (!dontDo)
|
|
{
|
|
// Get the root of the deployment on the destination server.
|
|
|
|
if (dstRoot == null)
|
|
{
|
|
if (createDst)
|
|
{
|
|
fgLogger.debug("Create destination parent folder:" + dstPath);
|
|
createDestination(remote, dstPath);
|
|
dstRoot = remote.lookup(-1, dstPath);
|
|
createdRoot = true;
|
|
}
|
|
else
|
|
{
|
|
throw new AVMNotFoundException("Node Not Found: " + dstRoot);
|
|
}
|
|
}
|
|
fgLogger.debug("create snapshot on remote");
|
|
snapshot = remote.createSnapshot(storePath[0], "PreDeploy", "Pre Deployment Snapshot").get(storePath[0]);
|
|
fgLogger.debug("snapshot created on remote");
|
|
}
|
|
|
|
final int srcVersion = version;
|
|
final String srcFinalPath = srcPath;
|
|
RetryingTransactionCallback<AVMNodeDescriptor> readRoot = new RetryingTransactionCallback<AVMNodeDescriptor>()
|
|
{
|
|
public AVMNodeDescriptor execute() throws Throwable
|
|
{
|
|
return fAVMService.lookup(srcVersion, srcFinalPath);
|
|
}
|
|
};
|
|
|
|
final AVMNodeDescriptor srcRoot = trn.doInTransaction(readRoot, true, true);
|
|
|
|
// Get the root of the deployment from this server.
|
|
// AVMNodeDescriptor srcRoot = fAVMService.lookup(version, srcPath);
|
|
|
|
if (srcRoot == null)
|
|
{
|
|
throw new AVMNotFoundException("Directory Not Found: " + srcPath);
|
|
}
|
|
if (!srcRoot.isDirectory())
|
|
{
|
|
throw new AVMWrongTypeException("Not a directory: " + srcPath);
|
|
}
|
|
|
|
/**
|
|
* The destination directory exists - check is actually a directory
|
|
*/
|
|
if (!dstRoot.isDirectory())
|
|
{
|
|
throw new AVMWrongTypeException("Not a Directory: " + dstPath);
|
|
}
|
|
|
|
try
|
|
{
|
|
/**
|
|
* Recursivly copy
|
|
*/
|
|
fgLogger.debug("both src and dest exist, recursivly deploy");
|
|
final AVMNodeDescriptor dstParentNode = dstRoot;
|
|
RetryingTransactionCallback<Integer> copyContentsRecursivly = new RetryingTransactionCallback<Integer>()
|
|
{
|
|
public Integer execute() throws Throwable
|
|
{
|
|
deployDirectoryPush(srcVersion, srcRoot, dstParentNode, remote, matcher, dontDelete, dontDo, callbacks);
|
|
return new Integer(0);
|
|
}
|
|
};
|
|
|
|
trn.setMaxRetries(1);
|
|
trn.doInTransaction(copyContentsRecursivly, false, true);
|
|
|
|
fgLogger.debug("finished copying, snapshot remote");
|
|
remote.createSnapshot(storePath[0], "Deployment", "Post Deployment Snapshot.");
|
|
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.END,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
dstPath);
|
|
processEvent(event, callbacks);
|
|
return;
|
|
}
|
|
catch (AVMException e)
|
|
{
|
|
fgLogger.debug("error during remote copy and snapshot");
|
|
try
|
|
{
|
|
if (snapshot != -1)
|
|
{
|
|
fgLogger.debug("Attempting to roll back ");
|
|
AVMSyncService syncService = getSyncService(hostName, port);
|
|
List<AVMDifference> diffs = syncService.compare(snapshot, dstPath, -1, dstPath, null);
|
|
syncService.update(diffs, null, false, false, true, true, "Aborted Deployment", "Aborted Deployment");
|
|
}
|
|
}
|
|
catch (Exception ee)
|
|
{
|
|
throw new AVMException("Failed to rollback to version " + snapshot + " on " + hostName, ee);
|
|
}
|
|
throw new AVMException("Deployment to " + hostName + " failed.", e);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.FAILED,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
dstPath, e.getMessage());
|
|
processEvent(event, callbacks);
|
|
|
|
throw new AVMException("Deployment to " + hostName + " failed." + e.toString(), e);
|
|
}
|
|
finally
|
|
{
|
|
fgLogger.debug("ASR Finally block, Releasing ASR deployment ticket");
|
|
fTicketHolder.setTicket(null);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
fgLogger.debug("about to release lock");
|
|
lock.releaseLock();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Deploy all the children of corresponding directories. (ASR version)
|
|
* @param src The source directory.
|
|
* @param dst The destination directory.
|
|
* @param remote The AVMRemote instance.
|
|
* @param dontDelete Flag for not deleting.
|
|
* @param dontDo Flag for dry run.
|
|
*/
|
|
private void deployDirectoryPush(int version,
|
|
AVMNodeDescriptor src,
|
|
AVMNodeDescriptor dst,
|
|
AVMRemote remote,
|
|
NameMatcher matcher,
|
|
boolean dontDelete, boolean dontDo,
|
|
List<DeploymentCallback> callbacks)
|
|
{
|
|
if (src.getGuid().equals(dst.getGuid()))
|
|
{
|
|
return;
|
|
}
|
|
if (!dontDo && !dontDelete)
|
|
{
|
|
copyMetadata(version, src, dst, remote);
|
|
}
|
|
// Get the listing for the source.
|
|
SortedMap<String, AVMNodeDescriptor> srcList = fAVMService.getDirectoryListing(src);
|
|
// Get the listing for the destination.
|
|
SortedMap<String, AVMNodeDescriptor> dstList = remote.getDirectoryListing(dst);
|
|
|
|
// Strip out stale nodes.
|
|
for (Map.Entry<String, AVMNodeDescriptor> entry : srcList.entrySet())
|
|
{
|
|
String name = entry.getKey();
|
|
AVMNodeDescriptor srcNode = entry.getValue();
|
|
|
|
if (isStale(srcNode))
|
|
{
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("Stale child found: " + srcNode);
|
|
}
|
|
srcList.remove(name);
|
|
}
|
|
}
|
|
|
|
for (Map.Entry<String, AVMNodeDescriptor> entry : srcList.entrySet())
|
|
{
|
|
String name = entry.getKey();
|
|
AVMNodeDescriptor srcNode = entry.getValue();
|
|
AVMNodeDescriptor dstNode = dstList.get(name);
|
|
if (!excluded(matcher, srcNode.getPath(), dstNode != null ? dstNode.getPath() : null))
|
|
{
|
|
if(isStale(srcNode))
|
|
{
|
|
fgLogger.debug("stale file not added" + srcNode);
|
|
continue;
|
|
}
|
|
|
|
deploySinglePush(version, srcNode, dst, dstNode, remote, matcher, dontDelete, dontDo, callbacks);
|
|
}
|
|
}
|
|
// Delete nodes that are missing in the source.
|
|
if (dontDelete)
|
|
{
|
|
return;
|
|
}
|
|
for (String name : dstList.keySet())
|
|
{
|
|
if (!srcList.containsKey(name))
|
|
{
|
|
Pair<Integer, String> source =
|
|
new Pair<Integer, String>(version, AVMNodeConverter.ExtendAVMPath(src.getPath(), name));
|
|
String destination = AVMNodeConverter.ExtendAVMPath(dst.getPath(), name);
|
|
if (!excluded(matcher, null, destination))
|
|
{
|
|
DeploymentEvent event =
|
|
new DeploymentEvent(DeploymentEvent.Type.DELETED,
|
|
source,
|
|
destination);
|
|
processEvent(event, callbacks);
|
|
if (dontDo)
|
|
{
|
|
continue;
|
|
}
|
|
remote.removeNode(dst.getPath(), name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push out a single node.
|
|
* @param src The source node.
|
|
* @param dstParent The destination parent.
|
|
* @param dst The destination node. May be null.
|
|
* @param remote The AVMRemote instance.
|
|
* @param dontDelete Flag for whether deletions should happen.
|
|
* @param dontDo Dry run flag.
|
|
*/
|
|
private void deploySinglePush(int version,
|
|
AVMNodeDescriptor src, AVMNodeDescriptor dstParent,
|
|
AVMNodeDescriptor dst, AVMRemote remote,
|
|
NameMatcher matcher,
|
|
boolean dontDelete, boolean dontDo,
|
|
List<DeploymentCallback> callbacks)
|
|
{
|
|
// Destination does not exist.
|
|
if (dst == null)
|
|
{
|
|
if (src.isDirectory())
|
|
{
|
|
// Recursively copy a source directory.
|
|
Pair<Integer, String> source =
|
|
new Pair<Integer, String>(version, src.getPath());
|
|
String destination = AVMNodeConverter.ExtendAVMPath(dstParent.getPath(), src.getName());
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.CREATED,
|
|
source,
|
|
destination);
|
|
processEvent(event, callbacks);
|
|
if (dontDo)
|
|
{
|
|
return;
|
|
}
|
|
copyDirectory(version, src, dstParent, remote, matcher, callbacks);
|
|
return;
|
|
}
|
|
|
|
// here when src is a file
|
|
Pair<Integer, String> source =
|
|
new Pair<Integer, String>(version, src.getPath());
|
|
String destination = AVMNodeConverter.ExtendAVMPath(dstParent.getPath(), src.getName());
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.CREATED,
|
|
source,
|
|
destination);
|
|
processEvent(event, callbacks);
|
|
if (dontDo)
|
|
{
|
|
return;
|
|
}
|
|
// Copy a source file.
|
|
OutputStream out = remote.createFile(dstParent.getPath(), src.getName());
|
|
try
|
|
{
|
|
InputStream in = fAVMService.getFileInputStream(src);
|
|
copyStream(in, out);
|
|
}
|
|
finally
|
|
{
|
|
if(out != null)
|
|
{
|
|
// whatever happens close stream
|
|
try
|
|
{
|
|
out.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new AVMException("I/O Exception", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
copyMetadata(version, src, remote.lookup(-1, dstParent.getPath() + '/' + src.getName()), remote);
|
|
return;
|
|
}
|
|
|
|
// Destination exists and is a directory.
|
|
if (src.isDirectory())
|
|
{
|
|
// If the destination is also a directory, recursively deploy.
|
|
if (dst.isDirectory())
|
|
{
|
|
deployDirectoryPush(version, src, dst, remote, matcher, dontDelete, dontDo, callbacks);
|
|
return;
|
|
}
|
|
Pair<Integer, String> source =
|
|
new Pair<Integer, String>(version, src.getPath());
|
|
String destination = dst.getPath();
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.CREATED,
|
|
source, destination);
|
|
processEvent(event, callbacks);
|
|
|
|
if (dontDo)
|
|
{
|
|
return;
|
|
}
|
|
// MER WHY IS THIS HERE ?
|
|
fgLogger.debug("Remove and recopy node :" + dstParent.getPath() + '/' + src.getName());
|
|
remote.removeNode(dstParent.getPath(), src.getName());
|
|
copyDirectory(version, src, dstParent, remote, matcher, callbacks);
|
|
return;
|
|
}
|
|
// Source exists and is a file.
|
|
if (dst.isFile())
|
|
{
|
|
// Destination is also a file. Overwrite if the GUIDS are different.
|
|
if (src.getGuid().equals(dst.getGuid()))
|
|
{
|
|
return;
|
|
}
|
|
Pair<Integer, String> source =
|
|
new Pair<Integer, String>(version, src.getPath());
|
|
String destination = dst.getPath();
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.UPDATED,
|
|
source,
|
|
destination);
|
|
processEvent(event, callbacks);
|
|
if (dontDo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OutputStream out = remote.getFileOutputStream(dst.getPath());
|
|
try
|
|
{
|
|
InputStream in = fAVMService.getFileInputStream(src);
|
|
copyStream(in, out);
|
|
}
|
|
finally
|
|
{
|
|
if(out != null)
|
|
{
|
|
// whatever happens close stream
|
|
try
|
|
{
|
|
out.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new AVMException("I/O Exception", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
copyMetadata(version, src, dst, remote);
|
|
return;
|
|
}
|
|
Pair<Integer, String> source =
|
|
new Pair<Integer, String>(version, src.getPath());
|
|
String destination = AVMNodeConverter.ExtendAVMPath(dstParent.getPath(), src.getName());
|
|
DeploymentEvent event = new DeploymentEvent(DeploymentEvent.Type.UPDATED,
|
|
source,
|
|
destination);
|
|
processEvent(event, callbacks);
|
|
if (dontDo)
|
|
{
|
|
return;
|
|
}
|
|
// Destination is a directory and the source is a file.
|
|
// Delete the destination directory and copy the file over.
|
|
remote.removeNode(dstParent.getPath(), dst.getName());
|
|
|
|
OutputStream out = remote.createFile(dstParent.getPath(), src.getName());
|
|
try
|
|
{
|
|
InputStream in = fAVMService.getFileInputStream(src);
|
|
copyStream(in, out);
|
|
}
|
|
finally
|
|
{
|
|
if(out != null)
|
|
{
|
|
// whatever happens close stream
|
|
try
|
|
{
|
|
out.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new AVMException("I/O Exception", e);
|
|
}
|
|
}
|
|
}
|
|
copyMetadata(version, src, remote.lookup(-1, dstParent.getPath() + '/' + dst.getName()), remote);
|
|
}
|
|
|
|
/**
|
|
* Recursively copy a directory.
|
|
* @param src
|
|
* @param parent
|
|
* @param remote
|
|
*/
|
|
private void copyDirectory(int version, AVMNodeDescriptor src, AVMNodeDescriptor parent,
|
|
AVMRemote remote, NameMatcher matcher, List<DeploymentCallback>callbacks)
|
|
{
|
|
// Create the destination directory.
|
|
remote.createDirectory(parent.getPath(), src.getName());
|
|
AVMNodeDescriptor newParent = remote.lookup(-1, parent.getPath() + '/' + src.getName());
|
|
copyMetadata(version, src, newParent, remote);
|
|
SortedMap<String, AVMNodeDescriptor> list =
|
|
fAVMService.getDirectoryListing(src);
|
|
// For each child in the source directory.
|
|
for (AVMNodeDescriptor child : list.values())
|
|
{
|
|
if (!excluded(matcher, child.getPath(), null))
|
|
{
|
|
/**
|
|
* Temporary work around for staleness.
|
|
*/
|
|
if (isStale(child))
|
|
{
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("Stale child found: " + child);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// If it's a file, copy it over and move on.
|
|
if (child.isFile())
|
|
{
|
|
DeploymentEvent event =
|
|
new DeploymentEvent(DeploymentEvent.Type.CREATED,
|
|
new Pair<Integer, String>(version, src.getPath() + '/' + child.getName()),
|
|
newParent.getPath() + '/' + child.getName());
|
|
processEvent(event, callbacks);
|
|
|
|
OutputStream out = remote.createFile(newParent.getPath(), child.getName());
|
|
try
|
|
{
|
|
InputStream in = fAVMService.getFileInputStream(child);
|
|
copyStream(in, out);
|
|
}
|
|
finally
|
|
{
|
|
if(out != null)
|
|
{
|
|
// whatever happens close stream
|
|
try
|
|
{
|
|
out.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new AVMException("I/O Exception", e);
|
|
}
|
|
}
|
|
}
|
|
copyMetadata(version, child, remote.lookup(-1, newParent.getPath() + '/' + child.getName()), remote);
|
|
}
|
|
else
|
|
{
|
|
// is a directory
|
|
DeploymentEvent event =
|
|
new DeploymentEvent(DeploymentEvent.Type.CREATED,
|
|
new Pair<Integer, String>(version, src.getPath() + '/' + child.getName() ),
|
|
newParent.getPath() + '/' + child.getName());
|
|
processEvent(event, callbacks);
|
|
// Otherwise copy the child directory recursively.
|
|
copyDirectory(version, child, newParent, remote, matcher, callbacks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility for copying from one stream to another.
|
|
*
|
|
* in is closed.
|
|
*
|
|
* out is not closed.
|
|
*
|
|
* @param in The input stream.
|
|
* @param out The output stream.
|
|
*/
|
|
private void copyStream(InputStream in, OutputStream out)
|
|
{
|
|
byte[] buff = new byte[8192];
|
|
int read = 0;
|
|
try
|
|
{
|
|
while ((read = in.read(buff)) != -1)
|
|
{
|
|
out.write(buff, 0, read);
|
|
}
|
|
in.close();
|
|
//out.flush();
|
|
//out.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new AVMException("I/O Exception", e);
|
|
}
|
|
}
|
|
|
|
private void copyMetadata(int version, AVMNodeDescriptor src, AVMNodeDescriptor dst, AVMRemote remote)
|
|
{
|
|
Map<QName, PropertyValue> props = fAVMService.getNodeProperties(version, src.getPath());
|
|
remote.setNodeProperties(dst.getPath(), props);
|
|
Set<QName> aspects = fAVMService.getAspects(version, src.getPath());
|
|
for (QName aspect : aspects)
|
|
{
|
|
if (remote.hasAspect(-1, dst.getPath(), aspect))
|
|
{
|
|
continue;
|
|
}
|
|
remote.addAspect(dst.getPath(), aspect);
|
|
}
|
|
remote.setGuid(dst.getPath(), src.getGuid());
|
|
if (src.isFile())
|
|
{
|
|
ContentData contData = fAVMService.getContentDataForRead(version, src.getPath());
|
|
remote.setEncoding(dst.getPath(), contData.getEncoding());
|
|
remote.setMimeType(dst.getPath(), contData.getMimetype());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility to get an AVMRemote from a remote Alfresco Server.
|
|
* @param hostName
|
|
* @param port
|
|
* @param userName
|
|
* @param password
|
|
* @return
|
|
*/
|
|
private AVMRemote getRemote(String hostName, int port, String userName, String password)
|
|
{
|
|
try
|
|
{
|
|
RmiProxyFactoryBean authFactory = new RmiProxyFactoryBean();
|
|
authFactory.setRefreshStubOnConnectFailure(true);
|
|
authFactory.setServiceInterface(AuthenticationService.class);
|
|
authFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/authentication");
|
|
authFactory.afterPropertiesSet();
|
|
AuthenticationService authService = (AuthenticationService)authFactory.getObject();
|
|
authService.authenticate(userName, password.toCharArray());
|
|
String ticket = authService.getCurrentTicket();
|
|
fTicketHolder.setTicket(ticket);
|
|
RmiProxyFactoryBean remoteFactory = new RmiProxyFactoryBean();
|
|
remoteFactory.setRefreshStubOnConnectFailure(true);
|
|
remoteFactory.setServiceInterface(AVMRemoteTransport.class);
|
|
remoteFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/avm");
|
|
remoteFactory.afterPropertiesSet();
|
|
AVMRemoteTransport transport = (AVMRemoteTransport)remoteFactory.getObject();
|
|
AVMRemoteImpl remote = new AVMRemoteImpl();
|
|
remote.setAvmRemoteTransport(transport);
|
|
remote.setClientTicketHolder(fTicketHolder);
|
|
return remote;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new AVMException("Could not Initialize Remote Connection to " + hostName, e);
|
|
}
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see org.alfresco.service.cmr.avm.deploy.DeploymentService#getRemoteActionService(java.lang.String, int, java.lang.String, java.lang.String)
|
|
*/
|
|
public ActionService getRemoteActionService(String hostName, int port, String userName, String password)
|
|
{
|
|
try
|
|
{
|
|
RmiProxyFactoryBean authFactory = new RmiProxyFactoryBean();
|
|
authFactory.setRefreshStubOnConnectFailure(true);
|
|
authFactory.setServiceInterface(AuthenticationService.class);
|
|
authFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/authentication");
|
|
authFactory.afterPropertiesSet();
|
|
AuthenticationService authService = (AuthenticationService)authFactory.getObject();
|
|
authService.authenticate(userName, password.toCharArray());
|
|
String ticket = authService.getCurrentTicket();
|
|
fTicketHolder.setTicket(ticket);
|
|
RmiProxyFactoryBean remoteFactory = new RmiProxyFactoryBean();
|
|
remoteFactory.setRefreshStubOnConnectFailure(true);
|
|
remoteFactory.setServiceInterface(ActionServiceTransport.class);
|
|
remoteFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/action");
|
|
remoteFactory.afterPropertiesSet();
|
|
ActionServiceTransport transport = (ActionServiceTransport)remoteFactory.getObject();
|
|
ActionServiceRemote remote = new ActionServiceRemote();
|
|
remote.setActionServiceTransport(transport);
|
|
remote.setClientTicketHolder(fTicketHolder);
|
|
return remote;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new AVMException("Could not Initialize Remote Connection to " + hostName, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility method to get the payload transformers for a named transport
|
|
*
|
|
* The transport adapters are sprung into the deploymentReceiverTransportAdapters property
|
|
*
|
|
* @return the transformers
|
|
*/
|
|
private List<DeploymentTransportOutputFilter> getTransformers(String transportName)
|
|
{
|
|
|
|
DeploymentReceiverTransportAdapter adapter = deploymentReceiverTransportAdapters.get(transportName);
|
|
|
|
if(adapter == null) {
|
|
// Adapter does not exist
|
|
fgLogger.error("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
|
|
throw new AVMException("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
|
|
}
|
|
|
|
List<DeploymentTransportOutputFilter> transformers = adapter.getTransformers();
|
|
return transformers;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Utility method to get a connection to a remote file system receiver (FSR)
|
|
*
|
|
* The transport adapters are sprung into the deploymentReceiverTransportAdapters property
|
|
* @param transportName the name of the adapter for the transport
|
|
* @param hostName the hostname or IP address to connect to
|
|
* @param port the port number
|
|
* @param version the version of the website to deploy
|
|
* @param srcPath the path of the website
|
|
*
|
|
* @return an implementation of the service
|
|
*/
|
|
private DeploymentReceiverService getDeploymentReceiverService(String transportName, String hostName, int port, int version, String srcPath)
|
|
{
|
|
|
|
DeploymentReceiverTransportAdapter adapter = deploymentReceiverTransportAdapters.get(transportName);
|
|
|
|
if(adapter == null) {
|
|
// Adapter does not exist
|
|
fgLogger.error("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
|
|
throw new AVMException("Deployment Receiver Transport adapter does not exist for transport. Name: " + transportName);
|
|
}
|
|
try
|
|
{
|
|
DeploymentReceiverTransport transport = adapter.getTransport(hostName, port, version, srcPath);
|
|
|
|
// Now decorate the transport with the service client
|
|
DeploymentReceiverServiceClient service = new DeploymentReceiverServiceClient();
|
|
service.setDeploymentReceiverTransport(transport);
|
|
return service;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new AVMException("Could not connect to remote deployment receiver, transportName:" + transportName + ", hostName:" + hostName + ", port: " + port, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility to get the sync service for rolling back after a failed deployment.
|
|
* @param hostName The target machine.
|
|
* @param port The port.
|
|
* @return An AVMSyncService instance.
|
|
*/
|
|
private AVMSyncService getSyncService(String hostName, int port)
|
|
{
|
|
try
|
|
{
|
|
RmiProxyFactoryBean syncFactory = new RmiProxyFactoryBean();
|
|
syncFactory.setRefreshStubOnConnectFailure(true);
|
|
syncFactory.setServiceInterface(AVMSyncServiceTransport.class);
|
|
syncFactory.setServiceUrl("rmi://" + hostName + ":" + port + "/avmsync");
|
|
syncFactory.afterPropertiesSet();
|
|
AVMSyncServiceTransport syncServiceTransport = (AVMSyncServiceTransport)syncFactory.getObject();
|
|
AVMSyncServiceRemote remote = new AVMSyncServiceRemote();
|
|
remote.setAvmSyncServiceTransport(syncServiceTransport);
|
|
remote.setClientTicketHolder(fTicketHolder);
|
|
return remote;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new AVMException("Could not roll back failed deployment to " + hostName, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to create a non existent destination.
|
|
* @param remote The AVMRemote instance.
|
|
* @param dstPath The destination path to create.
|
|
*/
|
|
private void createDestination(AVMRemote remote, String dstPath)
|
|
{
|
|
String[] storePath = dstPath.split(":");
|
|
String storeName = storePath[0];
|
|
String path = storePath[1];
|
|
AVMStoreDescriptor storeDesc = remote.getStore(storeName);
|
|
if (storeDesc == null)
|
|
{
|
|
remote.createStore(storeName);
|
|
}
|
|
SimplePath simpPath = new SimplePath(path);
|
|
if (simpPath.size() == 0)
|
|
{
|
|
return;
|
|
}
|
|
String prevPath = storeName + ":/";
|
|
for (int i = 0; i < simpPath.size(); i++)
|
|
{
|
|
String currPath = AVMNodeConverter.ExtendAVMPath(prevPath, simpPath.get(i));
|
|
AVMNodeDescriptor desc = remote.lookup(-1, currPath);
|
|
if (desc == null)
|
|
{
|
|
remote.createDirectory(prevPath, simpPath.get(i));
|
|
}
|
|
prevPath = currPath;
|
|
}
|
|
}
|
|
|
|
private Set<String>getAspects(AVMService avmService, AVMNodeDescriptor src)
|
|
{
|
|
Set<QName>aspects = avmService.getAspects(src);
|
|
Set<String>stringAspects = new HashSet<String>();
|
|
for (QName aspect : aspects)
|
|
{
|
|
stringAspects.add(aspect.toString());
|
|
}
|
|
return stringAspects;
|
|
}
|
|
|
|
private Map<String, Serializable> getProperties(AVMNodeDescriptor src, int version)
|
|
{
|
|
/**
|
|
* Get the AVM properties - which do not have any of the "syntetic" Node Service Values.
|
|
*/
|
|
Map<QName, PropertyValue> properties = fAVMService.getNodeProperties(src);
|
|
NodeRef nodeRef = AVMNodeConverter.ToNodeRef(version, src.getPath());
|
|
|
|
/**
|
|
* Get the properties in Node Service format
|
|
*/
|
|
Map<QName, Serializable> nodeProps = fAVMNodeService.getProperties(nodeRef);
|
|
|
|
Map<String, Serializable> retVal = new HashMap<String, Serializable>();
|
|
for(QName key : properties.keySet())
|
|
{
|
|
Serializable value = nodeProps.get(key);
|
|
retVal.put(key.toString(), value);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Deploy differences to a File System Receiver, FSR
|
|
*
|
|
* @param version snapshot version to deploy. If 0 then a new snapshot is created.
|
|
* @param srcPath
|
|
* @param adapterName
|
|
* @param hostName
|
|
* @param port
|
|
* @param userName
|
|
* @param password
|
|
* @param target
|
|
* @param matcher
|
|
* @param createDst Not implemented
|
|
* @param dontDelete Not implemented
|
|
* @param dontDo Not implemented
|
|
* @param callbacks Event callbacks when a deployment Starts, Ends, Adds, Deletes etc.
|
|
*
|
|
* @throws AVMException
|
|
*
|
|
* @see org.alfresco.service.cmr.avm.deploy.DeploymentService#deployDifferenceFS(int, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
|
|
*/
|
|
public void deployDifferenceFS(int version,
|
|
final String srcPath,
|
|
String adapterName,
|
|
String hostName,
|
|
int port,
|
|
String userName,
|
|
String password,
|
|
String target,
|
|
final NameMatcher matcher,
|
|
boolean createDst,
|
|
boolean dontDelete,
|
|
boolean dontDo,
|
|
List<DeploymentCallback> callbacks)
|
|
{
|
|
|
|
fgLogger.debug("deployDifferenceFS start");
|
|
/**
|
|
* Lock cluster for the remote target
|
|
*/
|
|
String lockStr = "deploy." + hostName + "." + port + "." + target;
|
|
QName lockQName = QName.createQName("{http://www.alfresco.org/deploymentService/1.0}" + lockStr);
|
|
final Lock lock = new Lock(lockQName);
|
|
lock.makeLock();
|
|
try
|
|
{
|
|
/**
|
|
* Cluster Lock held here
|
|
*/
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
Object[] objs = {version, srcPath, adapterName, hostName, port, target};
|
|
MessageFormat f = new MessageFormat("Deployment Lock Held: version {0}, srcPath {1}, adapterName {2}, hostName {3}, port {4}, target {5}");
|
|
fgLogger.debug(f.format(objs));
|
|
}
|
|
|
|
DeploymentReceiverService service = null;
|
|
List<DeploymentTransportOutputFilter>transformers = null;
|
|
String ticket = null;
|
|
|
|
String currentEffectiveUser = AuthenticationUtil.getRunAsUser();
|
|
|
|
try
|
|
{
|
|
// Kick off the event queue that will process deployment call-backs
|
|
final LinkedBlockingQueue<DeploymentEvent> eventQueue = new LinkedBlockingQueue<DeploymentEvent>();
|
|
EventQueueWorker eventQueueWorker = new EventQueueWorker(currentEffectiveUser, eventQueue, callbacks);
|
|
eventQueueWorker.setName(eventQueueWorker.getClass().getName());
|
|
eventQueueWorker.setPriority(Thread.currentThread().getPriority());
|
|
eventQueueWorker.start();
|
|
|
|
try
|
|
{
|
|
final String storeName = srcPath.substring(0, srcPath.indexOf(':'));
|
|
try {
|
|
|
|
if (version < 0)
|
|
{
|
|
RetryingTransactionHelper trn = trxService.getRetryingTransactionHelper();
|
|
|
|
RetryingTransactionCallback<Integer> localSnapshot = new RetryingTransactionCallback<Integer>()
|
|
{
|
|
public Integer execute() throws Throwable
|
|
{
|
|
int newVersion = fAVMService.createSnapshot(storeName, null, null).get(storeName);
|
|
return new Integer(newVersion);
|
|
}
|
|
};
|
|
version = trn.doInTransaction(localSnapshot, false, true).intValue();
|
|
fgLogger.debug("snapshot local created " + storeName + ", " + version);
|
|
}
|
|
|
|
transformers = getTransformers(adapterName);
|
|
service = getDeploymentReceiverService(adapterName, hostName, port, version, srcPath);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// unable to get service
|
|
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.FAILED,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
target, e.getMessage()));
|
|
throw e;
|
|
}
|
|
|
|
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.START,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
target));
|
|
|
|
// Go parallel to reduce the problems of high network latency
|
|
|
|
final LinkedBlockingQueue<DeploymentWork> sendQueue = new LinkedBlockingQueue<DeploymentWork>();
|
|
final List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>());
|
|
|
|
SendQueueWorker[] workers = new SendQueueWorker[numberOfSendingThreads];
|
|
for(int i = 0; i < numberOfSendingThreads; i++)
|
|
{
|
|
workers[i] = new SendQueueWorker(currentEffectiveUser, service, fAVMService, trxService, errors, eventQueue, sendQueue, transformers);
|
|
workers[i].setName(workers[i].getClass().getName());
|
|
workers[i].setPriority(Thread.currentThread().getPriority());
|
|
}
|
|
|
|
for(SendQueueWorker sender : workers)
|
|
{
|
|
sender.start();
|
|
}
|
|
|
|
try
|
|
{
|
|
fgLogger.debug("calling begin");
|
|
DeploymentToken token = service.begin(target, storeName, version, userName, password.toCharArray());
|
|
ticket = token.getTicket();
|
|
|
|
lock.checkLock();
|
|
|
|
// run this in its own txn
|
|
final DeploymentReceiverService fservice = service;
|
|
final String fTicket = ticket;
|
|
final int fVersion = version;
|
|
RetryingTransactionCallback<Integer> pushFSR = new RetryingTransactionCallback<Integer>()
|
|
{
|
|
public Integer execute() throws Throwable
|
|
{
|
|
deployDirectoryPushFSR(fservice, fTicket, fVersion, srcPath, "/", matcher, eventQueue, sendQueue, errors, lock);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
RetryingTransactionHelper trn = trxService.getRetryingTransactionHelper();
|
|
trn.doInTransaction(pushFSR, false, true);
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
errors.add(e);
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
errors.add(new AVMException("Unexpected Throwable", t));
|
|
}
|
|
finally
|
|
{
|
|
// clean up senders thread pool
|
|
fgLogger.debug("closing deployment workers");
|
|
for(SendQueueWorker sender : workers)
|
|
{
|
|
sender.stopMeWhenIdle();
|
|
}
|
|
for(SendQueueWorker sender : workers)
|
|
{
|
|
sender.join();
|
|
}
|
|
fgLogger.debug("deployment workers closed");
|
|
|
|
if (errors.size() <= 0 && ticket != null)
|
|
{
|
|
try
|
|
{
|
|
fgLogger.debug("no errors - prepare and commit");
|
|
lock.checkLock();
|
|
|
|
service.prepare(ticket);
|
|
lock.checkLock();
|
|
|
|
service.commit(ticket);
|
|
// no point checking the lock here - we have committed.
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
errors.add(e);
|
|
}
|
|
}
|
|
|
|
if(errors.size() > 0)
|
|
{
|
|
fgLogger.debug("errors on deployment workers");
|
|
Exception firstError = errors.get(0);
|
|
|
|
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.FAILED,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
target, firstError.getMessage()));
|
|
|
|
if (ticket != null)
|
|
{
|
|
try
|
|
{
|
|
service.abort(ticket);
|
|
}
|
|
catch (Exception ae)
|
|
{
|
|
// nothing we can do here
|
|
fgLogger.error("Unable to abort deployment. Error in exception handler", ae);
|
|
}
|
|
}
|
|
// yes there were errors, throw the first exception that was saved
|
|
MessageFormat f = new MessageFormat("Error during deployment srcPath: {0}, version:{1}, adapterName:{2}, hostName:{3}, port:{4}, error:{5}");
|
|
Object[] objs = { srcPath, version, adapterName, hostName, port, firstError };
|
|
|
|
throw new AVMException(f.format(objs), firstError);
|
|
}
|
|
} // end of finally block
|
|
|
|
// Success if we get here
|
|
eventQueue.add(new DeploymentEvent(DeploymentEvent.Type.END,
|
|
new Pair<Integer, String>(version, srcPath),
|
|
target));
|
|
|
|
fgLogger.debug("deployment completed successfully");
|
|
}
|
|
finally
|
|
{
|
|
// Now stutdown the event queue
|
|
fgLogger.debug("closing event queue");
|
|
eventQueueWorker.stopMeWhenIdle();
|
|
eventQueueWorker.join();
|
|
fgLogger.debug("event queue closed");
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// yes there were errors
|
|
MessageFormat f = new MessageFormat("Deployment exception, unable to deploy : srcPath:{0}, target:{1}, version:{2}, adapterName:{3}, hostName:{4}, port:{5}, error:{6}");
|
|
Object[] objs = { srcPath, target, version, adapterName, hostName, port, e };
|
|
throw new AVMException(f.format(objs), e);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
fgLogger.debug("At end of method - about to release lock");
|
|
lock.releaseLock();
|
|
}
|
|
} // End of deploy difference FS
|
|
|
|
|
|
private class ComparatorFileDescriptorCaseSensitive implements Comparator<FileDescriptor>
|
|
{
|
|
public int compare(FileDescriptor o1, FileDescriptor o2)
|
|
{
|
|
return o1.getName().compareTo(o2.getName());
|
|
}
|
|
}
|
|
|
|
private class ComparatorAVMNodeDescriptorCaseSensitive implements Comparator<AVMNodeDescriptor>
|
|
{
|
|
public int compare(AVMNodeDescriptor o1, AVMNodeDescriptor o2)
|
|
{
|
|
return o1.getName().compareTo(o2.getName());
|
|
}
|
|
}
|
|
|
|
private ComparatorFileDescriptorCaseSensitive FILE_DESCRIPTOR_CASE_SENSITIVE = new ComparatorFileDescriptorCaseSensitive();
|
|
private ComparatorAVMNodeDescriptorCaseSensitive AVM_DESCRIPTOR_CASE_SENSITIVE = new ComparatorAVMNodeDescriptorCaseSensitive();
|
|
|
|
/**
|
|
* deployDirectoryPush (FSR only)
|
|
*
|
|
* Compares the source and destination listings and updates report with update events required to make
|
|
* dest similar to src.
|
|
*
|
|
* @param service
|
|
* @param ticket
|
|
* @param report
|
|
* @param callbacks
|
|
* @param version
|
|
* @param srcPath
|
|
* @param dstPath
|
|
* @param matcher
|
|
*/
|
|
private void deployDirectoryPushFSR(DeploymentReceiverService service,
|
|
String ticket,
|
|
int version,
|
|
String srcPath,
|
|
String dstPath,
|
|
NameMatcher matcher,
|
|
BlockingQueue<DeploymentEvent> eventQueue,
|
|
BlockingQueue<DeploymentWork> sendQueue,
|
|
List<Exception> errors,
|
|
Lock lock)
|
|
{
|
|
Map<String, AVMNodeDescriptor> rawSrcListing = fAVMService.getDirectoryListing(version, srcPath);
|
|
List<FileDescriptor> rawDstListing = service.getListing(ticket, dstPath);
|
|
|
|
// Need to change from case insensitive order to case sensitive order
|
|
TreeSet<FileDescriptor> dstListing = new TreeSet<FileDescriptor>(FILE_DESCRIPTOR_CASE_SENSITIVE);
|
|
dstListing.addAll(rawDstListing);
|
|
|
|
TreeSet<AVMNodeDescriptor> srcListing = new TreeSet<AVMNodeDescriptor>(AVM_DESCRIPTOR_CASE_SENSITIVE);
|
|
srcListing.addAll(rawSrcListing.values());
|
|
|
|
Iterator<FileDescriptor> dstIter = dstListing.iterator();
|
|
Iterator<AVMNodeDescriptor> srcIter = srcListing.iterator();
|
|
|
|
lock.checkLock();
|
|
|
|
// Here with two sorted directory listings
|
|
AVMNodeDescriptor src = null;
|
|
FileDescriptor dst = null;
|
|
|
|
// Step through both directory listings
|
|
while ((srcIter.hasNext() || dstIter.hasNext() || src != null || dst != null) && errors.size() <= 0)
|
|
{
|
|
if (src == null)
|
|
{
|
|
if (srcIter.hasNext())
|
|
{
|
|
src = srcIter.next();
|
|
|
|
/**
|
|
* Temporary check for stale assets
|
|
*
|
|
* Correct fix would be to remove stale files from the snapshot.
|
|
* Code becomes obsolete once stale files are not part of the snapshot.
|
|
*/
|
|
if (isStale(src))
|
|
{
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("Stale child found: " + src);
|
|
}
|
|
src = null;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (dst == null)
|
|
{
|
|
if (dstIter.hasNext())
|
|
{
|
|
dst = dstIter.next();
|
|
}
|
|
}
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("comparing src:" + src + " dst:"+ dst);
|
|
}
|
|
|
|
lock.checkLock();
|
|
|
|
// This means no entry on src so delete what is on dst.
|
|
if (src == null)
|
|
{
|
|
String newDstPath = extendPath(dstPath, dst.getName());
|
|
if (!excluded(matcher, null, newDstPath))
|
|
{
|
|
sendQueue.add(new DeploymentWork(new DeploymentEvent(DeploymentEvent.Type.DELETED,
|
|
new Pair<Integer, String>(version, extendPath(srcPath, dst.getName())),
|
|
newDstPath), ticket));
|
|
}
|
|
dst = null;
|
|
continue;
|
|
}
|
|
// Nothing on the destination so copy over.
|
|
if (dst == null)
|
|
{
|
|
if (!excluded(matcher, src.getPath(), null))
|
|
{
|
|
createOnFSR(service, ticket, version, src, dstPath, matcher, sendQueue);
|
|
}
|
|
src = null;
|
|
continue;
|
|
}
|
|
|
|
// Here with src and dst containing something
|
|
int diff = src.getName().compareTo(dst.getName());
|
|
if (diff < 0)
|
|
{
|
|
// src is less than dst - must be new content in src
|
|
if (!excluded(matcher, src.getPath(), null))
|
|
{
|
|
createOnFSR(service, ticket, version, src, dstPath, matcher, sendQueue);
|
|
}
|
|
src = null;
|
|
continue;
|
|
}
|
|
if (diff == 0)
|
|
{
|
|
/**
|
|
* src and dst have same file name and GUID - nothing to do
|
|
*/
|
|
if (src.getGuid().equals(dst.getGUID()))
|
|
{
|
|
src = null;
|
|
dst = null;
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
* src and dst are different and src is a file
|
|
*/
|
|
if (src.isFile())
|
|
{
|
|
// this is an update to a file
|
|
String extendedPath = extendPath(dstPath, dst.getName());
|
|
if (!excluded(matcher, src.getPath(), extendedPath))
|
|
{
|
|
// Work in progress
|
|
sendQueue.add(new DeploymentWork(
|
|
new DeploymentEvent(DeploymentEvent.Type.UPDATED,
|
|
new Pair<Integer, String>(version, src.getPath()),
|
|
extendedPath), ticket, src, version));
|
|
}
|
|
src = null;
|
|
dst = null;
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
* src and dst are different and src is a directory
|
|
*/
|
|
if (dst.getType() == FileType.DIR)
|
|
{
|
|
String extendedPath = extendPath(dstPath, dst.getName());
|
|
|
|
Set<String>stringAspects = getAspects(fAVMService, src);
|
|
Map<String, Serializable> stringProperties = getProperties(src, version);
|
|
|
|
/**
|
|
* Update the directory before any children
|
|
*/
|
|
service.updateDirectory(ticket, extendedPath, src.getGuid(), stringAspects, stringProperties);
|
|
|
|
if (!excluded(matcher, src.getPath(), extendedPath))
|
|
{
|
|
deployDirectoryPushFSR(service, ticket, version, src.getPath(), extendedPath, matcher, eventQueue, sendQueue, errors, lock);
|
|
}
|
|
|
|
src = null;
|
|
dst = null;
|
|
continue;
|
|
}
|
|
if (!excluded(matcher, src.getPath(), null))
|
|
{
|
|
createOnFSR(service, ticket, version, src, dstPath, matcher, sendQueue);
|
|
}
|
|
src = null;
|
|
dst = null;
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
* diff > 0
|
|
* Destination is missing in source, delete it.
|
|
*/
|
|
String newDstPath = extendPath(dstPath, dst.getName());
|
|
|
|
sendQueue.add(new DeploymentWork(new DeploymentEvent(DeploymentEvent.Type.DELETED,
|
|
new Pair<Integer, String>(version, extendPath(srcPath, dst.getName())),
|
|
newDstPath), ticket));
|
|
|
|
//
|
|
|
|
dst = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy a file or directory to an empty destination on an FSR
|
|
* @param service
|
|
* @param ticket
|
|
* @param report
|
|
* @param callback
|
|
* @param version
|
|
* @param src
|
|
* @param parentPath
|
|
*/
|
|
private void createOnFSR(DeploymentReceiverService service,
|
|
String ticket,
|
|
int version,
|
|
AVMNodeDescriptor src,
|
|
String parentPath,
|
|
NameMatcher matcher,
|
|
BlockingQueue<DeploymentWork> sendQueue)
|
|
{
|
|
String dstPath = extendPath(parentPath, src.getName());
|
|
|
|
// Need to queue the request to copy file or dir to remote.
|
|
sendQueue.add(new DeploymentWork(
|
|
new DeploymentEvent(DeploymentEvent.Type.CREATED,
|
|
new Pair<Integer, String>(version, src.getPath()),
|
|
dstPath), ticket, src, version));
|
|
|
|
if (src.isFile())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// here if src is a directory.
|
|
|
|
// Need to create directories in controlling thread since it needs to be created
|
|
// BEFORE any children are written
|
|
Set<String>stringAspects = getAspects(fAVMService, src);
|
|
Map<String, Serializable> stringProperties = getProperties(src, version);
|
|
|
|
service.createDirectory(ticket, dstPath, src.getGuid(), stringAspects, stringProperties);
|
|
|
|
// now copy the children over
|
|
Map<String, AVMNodeDescriptor> listing = fAVMService.getDirectoryListing(src);
|
|
for (AVMNodeDescriptor child : listing.values())
|
|
{
|
|
if (!excluded(matcher, child.getPath(), null))
|
|
{
|
|
if (isStale(child))
|
|
{
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("Stale child found: " + child);
|
|
}
|
|
continue;
|
|
}
|
|
createOnFSR(service, ticket, version, child, dstPath, matcher, sendQueue);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void processEvent(DeploymentEvent event, List<DeploymentCallback> callbacks)
|
|
{
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug(event);
|
|
}
|
|
if (callbacks != null)
|
|
{
|
|
for (DeploymentCallback callback : callbacks)
|
|
{
|
|
callback.eventOccurred(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extend a path.
|
|
* @param path
|
|
* @param name
|
|
* @return
|
|
*/
|
|
private String extendPath(String path, String name)
|
|
{
|
|
if (path.endsWith("/"))
|
|
{
|
|
return path + name;
|
|
}
|
|
return path + '/' + name;
|
|
}
|
|
|
|
/**
|
|
* Returns true if either srcPath or dstPath are matched by matcher.
|
|
* @param matcher
|
|
* @param srcPath
|
|
* @param dstPath
|
|
* @return
|
|
*/
|
|
private boolean excluded(NameMatcher matcher, String srcPath, String dstPath)
|
|
{
|
|
return matcher != null && ((srcPath != null && matcher.matches(srcPath)) || (dstPath != null && matcher.matches(dstPath)));
|
|
}
|
|
|
|
private Map<String, DeploymentReceiverTransportAdapter> deploymentReceiverTransportAdapters;
|
|
/**
|
|
* The deployment transport adapters provide the factories used to connect to a remote file system receiver.
|
|
*/
|
|
public void setDeploymentReceiverTransportAdapters(Map<String, DeploymentReceiverTransportAdapter> adapters) {
|
|
this.deploymentReceiverTransportAdapters=adapters;
|
|
}
|
|
|
|
public Map<String, DeploymentReceiverTransportAdapter> getDeploymentReceiverTransportAdapters() {
|
|
return this.deploymentReceiverTransportAdapters;
|
|
}
|
|
|
|
public Set<String> getAdapterNames()
|
|
{
|
|
if(deploymentReceiverTransportAdapters != null) {
|
|
return(deploymentReceiverTransportAdapters.keySet());
|
|
}
|
|
else
|
|
{
|
|
Set<String> ret = new HashSet<String>(1);
|
|
ret.add("default");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
public List<NodeRef> findLiveDeploymentServers(NodeRef webProjectRef)
|
|
{
|
|
return findDeploymentServers(webProjectRef, true, false);
|
|
}
|
|
|
|
public List<NodeRef> findTestDeploymentServers(NodeRef webProjectRef, boolean availableOnly)
|
|
{
|
|
return findDeploymentServers(webProjectRef, false, availableOnly);
|
|
}
|
|
|
|
private List<NodeRef> findDeploymentServers(NodeRef webProjectRef, boolean live, boolean availableOnly)
|
|
{
|
|
|
|
Path projectPath = nodeService.getPath(webProjectRef);
|
|
String stringPath = projectPath.toPrefixString(namespacePrefixResolver);
|
|
String serverType;
|
|
|
|
if (live)
|
|
{
|
|
serverType = WCMAppModel.CONSTRAINT_LIVESERVER;
|
|
}
|
|
else
|
|
{
|
|
serverType = WCMAppModel.CONSTRAINT_TESTSERVER;
|
|
}
|
|
|
|
|
|
StringBuilder query = new StringBuilder("PATH:\"");
|
|
|
|
query.append(stringPath);
|
|
query.append("/*\" ");
|
|
query.append(" AND @");
|
|
query.append(NamespaceService.WCMAPP_MODEL_PREFIX);
|
|
query.append("\\:");
|
|
query.append(WCMAppModel.PROP_DEPLOYSERVERTYPE.getLocalName());
|
|
query.append(":\"");
|
|
query.append(serverType);
|
|
query.append("\"");
|
|
|
|
// if required filter the test servers
|
|
if (live == false && availableOnly)
|
|
{
|
|
query.append(" AND ISNULL:\"");
|
|
query.append(WCMAppModel.PROP_DEPLOYSERVERALLOCATEDTO.toString());
|
|
query.append("\"");
|
|
}
|
|
|
|
if (fgLogger.isDebugEnabled())
|
|
fgLogger.debug("Finding deployment servers using query: " + query.toString());
|
|
|
|
// execute the query
|
|
ResultSet results = null;
|
|
List<NodeRef> servers = new ArrayList<NodeRef>();
|
|
try
|
|
{
|
|
results = searchService.query(webProjectRef.getStoreRef(),
|
|
SearchService.LANGUAGE_LUCENE, query.toString());
|
|
|
|
if (fgLogger.isDebugEnabled())
|
|
fgLogger.debug("Found " + results.length() + " deployment servers");
|
|
|
|
for (NodeRef server : results.getNodeRefs())
|
|
{
|
|
servers.add(server);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (results != null)
|
|
{
|
|
results.close();
|
|
}
|
|
}
|
|
|
|
return servers;
|
|
}
|
|
|
|
public void setNumberOfSendingThreads(int numberOfSendingThreads) {
|
|
this.numberOfSendingThreads = numberOfSendingThreads;
|
|
}
|
|
|
|
public int getNumberOfSendingThreads() {
|
|
return numberOfSendingThreads;
|
|
}
|
|
|
|
public void setJobLockService(JobLockService jobLockService) {
|
|
this.jobLockService = jobLockService;
|
|
}
|
|
|
|
public JobLockService getJobLockService() {
|
|
return jobLockService;
|
|
}
|
|
|
|
public void setTargetLockTimeToLive(long targetLockTimeToLive) {
|
|
this.targetLockTimeToLive = targetLockTimeToLive;
|
|
}
|
|
|
|
public long getTargetLockTimeToLive() {
|
|
return targetLockTimeToLive;
|
|
}
|
|
|
|
public void setTargetLockRetryWait(long targetLockRetryWait) {
|
|
this.targetLockRetryWait = targetLockRetryWait;
|
|
}
|
|
|
|
public long getTargetLockRetryWait() {
|
|
return targetLockRetryWait;
|
|
}
|
|
|
|
public void setTargetLockRetryCount(int targetLockRetryCount) {
|
|
this.targetLockRetryCount = targetLockRetryCount;
|
|
}
|
|
|
|
public int getTargetLockRetryCount() {
|
|
return targetLockRetryCount;
|
|
}
|
|
|
|
public void setAvmNodeService(AVMNodeService fAVMNodeService) {
|
|
this.fAVMNodeService = fAVMNodeService;
|
|
}
|
|
|
|
public AVMNodeService getAvmNodeService() {
|
|
return fAVMNodeService;
|
|
}
|
|
|
|
public void setOutputBufferSize(int outputBufferSize) {
|
|
this.outputBufferSize = outputBufferSize;
|
|
}
|
|
|
|
public int getOutputBufferSize() {
|
|
return outputBufferSize;
|
|
}
|
|
|
|
/**
|
|
* This thread processes the event queue to do the callbacks
|
|
* @author mrogers
|
|
*
|
|
*/
|
|
private class EventQueueWorker extends Thread
|
|
{
|
|
private BlockingQueue<DeploymentEvent> eventQueue;
|
|
private List<DeploymentCallback> callbacks;
|
|
private String userName;
|
|
|
|
private boolean stopMe = false;
|
|
|
|
EventQueueWorker(String userName, BlockingQueue<DeploymentEvent> eventQueue, List<DeploymentCallback> callbacks)
|
|
{
|
|
this.eventQueue = eventQueue;
|
|
this.callbacks = callbacks;
|
|
this.userName = userName;
|
|
}
|
|
|
|
public void run()
|
|
{
|
|
AuthenticationUtil.setFullyAuthenticatedUser(userName);
|
|
|
|
while (true)
|
|
{
|
|
DeploymentEvent event = null;
|
|
try {
|
|
event = eventQueue.poll(3, TimeUnit.SECONDS);
|
|
} catch (InterruptedException e1) {
|
|
fgLogger.debug("Interrupted ", e1);
|
|
}
|
|
|
|
if(event == null)
|
|
{
|
|
if(stopMe)
|
|
{
|
|
fgLogger.debug("Event Queue Closing Normally");
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug(event);
|
|
}
|
|
if (callbacks != null)
|
|
{
|
|
for (DeploymentCallback callback : callbacks)
|
|
{
|
|
callback.eventOccurred(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void stopMeWhenIdle()
|
|
{
|
|
stopMe = true;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
* Inner Class to Decorate the jobLockService to
|
|
* add control over the refreshLock behaviour.
|
|
*
|
|
* Deployment service calls (On deployment main thread)
|
|
* makeLock and releaseLock around the deployment.
|
|
* periodically calls checkLock as it does its work.
|
|
* checkLock can throw an exception if the business process has timed out.
|
|
*
|
|
* isActive and lockReleased called by Job Lock Thread
|
|
*/
|
|
private class Lock implements JobLockRefreshCallback
|
|
{
|
|
/**
|
|
* The name of the lock - unique for each target
|
|
*/
|
|
QName lockQName;
|
|
|
|
/**
|
|
* The unique token for this lock instance.
|
|
*/
|
|
String lockToken;
|
|
|
|
/**
|
|
* Is the lock active ?
|
|
*/
|
|
boolean active = false;
|
|
|
|
/**
|
|
* When did we last check whether the lock is active
|
|
*/
|
|
Date lastActive = new Date();
|
|
|
|
public Lock(QName lockQName)
|
|
{
|
|
this.lockQName = lockQName;
|
|
}
|
|
|
|
/**
|
|
* Make the lock - called on main deployment thread
|
|
*
|
|
* @throws LockAquisitionException
|
|
*/
|
|
public void makeLock()
|
|
{
|
|
if(fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("target lock refresh time :" + getTargetLockRefreshTime() + "targetLockRetryWait:" + targetLockRetryWait + "targetLockRetryCount:" + targetLockRetryCount);
|
|
}
|
|
lockToken = jobLockService.getLock(lockQName, targetLockRefreshTime, targetLockRetryWait, targetLockRetryCount);
|
|
|
|
synchronized(this)
|
|
{
|
|
active = true;
|
|
}
|
|
if (fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("lock taken:" + lockQName);
|
|
}
|
|
|
|
// We may have taken so long to begin that we have already timed out !
|
|
checkLock();
|
|
|
|
fgLogger.debug("register lock callback, target lock refresh time :" + getTargetLockRefreshTime());
|
|
jobLockService.refreshLock(lockToken, lockQName, getTargetLockRefreshTime(), this);
|
|
fgLogger.debug("callback registered");
|
|
}
|
|
|
|
/**
|
|
* Refresh the lock - called as the business process progresses.
|
|
*
|
|
* Called on main deployment thread.
|
|
* @throws AVMException (Lock timeout)
|
|
*/
|
|
public void checkLock()
|
|
{
|
|
// Do I need to sync this?
|
|
|
|
if(active)
|
|
{
|
|
Date now = new Date();
|
|
|
|
if(now.getTime() > lastActive.getTime() + targetLockTimeToLive)
|
|
{
|
|
// lock time to live has expired.
|
|
MessageFormat f = new MessageFormat("Deployment Lock timeout, lock time to live exceeded, timeout:{0}mS time since last activity:{1}mS");
|
|
Object[] objs = {new Long(targetLockTimeToLive), new Long(now.getTime() - lastActive.getTime()) };
|
|
throw new AVMException(f.format(objs));
|
|
}
|
|
|
|
// Update lastActive to 1S boundary
|
|
if(now.getTime() > lastActive.getTime() + 1000)
|
|
{
|
|
lastActive = new Date();
|
|
fgLogger.debug("lastActive:" + lastActive);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// lock not active. Has been switched off by Job Lock Service.
|
|
MessageFormat f = new MessageFormat("Lock timeout, lock not active");
|
|
Object[] objs = { };
|
|
throw new AVMException(f.format(objs));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Release the lock
|
|
*
|
|
* Called on main deployment thread
|
|
*/
|
|
public void releaseLock()
|
|
{
|
|
if(fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("deployment service about to releaseLock : " + lockQName);
|
|
}
|
|
if(active)
|
|
{
|
|
jobLockService.releaseLock(lockToken, lockQName);
|
|
}
|
|
fgLogger.debug("setting active = false" + lockQName);
|
|
|
|
// may need to sync this
|
|
synchronized(this)
|
|
{
|
|
active = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job Lock Callback
|
|
*
|
|
* Callback from the job lock service. Is the deployment active?
|
|
*/
|
|
@Override
|
|
public boolean isActive()
|
|
{
|
|
Date now = new Date();
|
|
|
|
synchronized(this)
|
|
{
|
|
if(now.getTime() > lastActive.getTime() + targetLockTimeToLive)
|
|
{
|
|
active = false;
|
|
}
|
|
|
|
// may need to sync active flag
|
|
if(fgLogger.isDebugEnabled())
|
|
{
|
|
fgLogger.debug("deployment service callback active: " + active);
|
|
}
|
|
|
|
return active;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job Lock Callback.
|
|
*/
|
|
@Override
|
|
public void lockReleased()
|
|
{
|
|
fgLogger.debug("deployment service: lock released callback");
|
|
synchronized(this)
|
|
{
|
|
active = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* This thread processes the send queue
|
|
* @author mrogers
|
|
*
|
|
*/
|
|
private class SendQueueWorker extends Thread
|
|
{
|
|
private BlockingQueue<DeploymentEvent> eventQueue;
|
|
private BlockingQueue<DeploymentWork> sendQueue;
|
|
private DeploymentReceiverService service;
|
|
private String userName;
|
|
private AVMService avmService;
|
|
private TransactionService trxService;
|
|
List<Exception> errors;
|
|
List<DeploymentTransportOutputFilter> transformers;
|
|
|
|
private boolean stopMe = false;
|
|
|
|
SendQueueWorker(String userName,
|
|
DeploymentReceiverService service,
|
|
AVMService avmService,
|
|
TransactionService trxService,
|
|
List<Exception> errors,
|
|
BlockingQueue<DeploymentEvent> eventQueue,
|
|
BlockingQueue<DeploymentWork> sendQueue,
|
|
List<DeploymentTransportOutputFilter> transformers
|
|
)
|
|
{
|
|
this.eventQueue = eventQueue;
|
|
this.sendQueue = sendQueue;
|
|
this.service = service;
|
|
this.avmService = avmService;
|
|
this.trxService = trxService;
|
|
this.errors = errors;
|
|
this.transformers = transformers;
|
|
this.userName = userName;
|
|
}
|
|
|
|
public void run()
|
|
{
|
|
AuthenticationUtil.setFullyAuthenticatedUser(userName);
|
|
|
|
while (errors.size() <= 0)
|
|
{
|
|
DeploymentWork work = null;
|
|
try {
|
|
work = sendQueue.poll(3, TimeUnit.SECONDS);
|
|
} catch (InterruptedException e1) {
|
|
fgLogger.debug("Interrupted ", e1);
|
|
continue;
|
|
}
|
|
|
|
if(work == null)
|
|
{
|
|
if(stopMe)
|
|
{
|
|
fgLogger.debug("Send Queue Worker Closing Normally");
|
|
eventQueue = null;
|
|
sendQueue = null;
|
|
service = null;
|
|
errors = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(work != null)
|
|
{
|
|
DeploymentEvent event = work.getEvent();
|
|
String ticket = work.getTicket();
|
|
try
|
|
{
|
|
if(event.getType().equals(DeploymentEvent.Type.DELETED))
|
|
{
|
|
service.delete(ticket, event.getDestination());
|
|
}
|
|
else if (event.getType().equals(DeploymentEvent.Type.CREATED))
|
|
{
|
|
AVMNodeDescriptor src = work.getSrc();
|
|
if(src.isFile())
|
|
{
|
|
copyFileToFSR(src, true, work.getVersion(), event.getDestination(), ticket);
|
|
}
|
|
else
|
|
{
|
|
// Do nothing. mkdir done on main thread.
|
|
//makeDirectoryOnFSR(src, event.getDestination(), ticket);
|
|
}
|
|
}
|
|
else if (event.getType().equals(DeploymentEvent.Type.UPDATED))
|
|
{
|
|
copyFileToFSR(work.getSrc(), false, work.getVersion(), event.getDestination(), ticket);
|
|
}
|
|
// success, now put the event onto the event queue
|
|
eventQueue.add(event);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
errors.add(e);
|
|
}
|
|
}
|
|
}
|
|
fgLogger.debug("Send Queue Worker finished");
|
|
}
|
|
|
|
public void stopMeWhenIdle()
|
|
{
|
|
stopMe = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create or update a single file on a remote FSR.
|
|
* @param ticket
|
|
* @param src which file to copy
|
|
* @param dstPath where to copy the file
|
|
*/
|
|
private void copyFileToFSR(
|
|
final AVMNodeDescriptor src,
|
|
final boolean create,
|
|
final int version,
|
|
final String dstPath,
|
|
final String ticket)
|
|
{
|
|
try
|
|
{
|
|
// Perform copy within 'read only' transaction
|
|
RetryingTransactionHelper trx = trxService.getRetryingTransactionHelper();
|
|
trx.setMaxRetries(1);
|
|
trx.doInTransaction(new RetryingTransactionCallback<Boolean>()
|
|
{
|
|
public Boolean execute() throws Exception
|
|
{
|
|
ContentData data = avmService.getContentDataForRead(src);
|
|
InputStream in = avmService.getFileInputStream(src);
|
|
String encoding = data.getEncoding();
|
|
String mimeType = data.getMimetype();
|
|
|
|
Set<String>stringAspects = getAspects(avmService, src);
|
|
Map<String, Serializable> stringProperties = getProperties(src, version);
|
|
OutputStream out = service.send(ticket, create, dstPath, src.getGuid(), encoding, mimeType, stringAspects, stringProperties);
|
|
|
|
try
|
|
{
|
|
// Buffer the output, we don't want to send lots of small packets
|
|
out = new BufferedOutputStream(out, outputBufferSize);
|
|
|
|
// Call content transformers here to transform from local to network format
|
|
if(transformers != null && transformers.size() > 0) {
|
|
// yes we have pay-load transformers
|
|
for(DeploymentTransportOutputFilter transformer : transformers)
|
|
{
|
|
out = transformer.addFilter(out, src.getPath(), encoding, mimeType);
|
|
}
|
|
}
|
|
|
|
copyStream(in, out);
|
|
}
|
|
finally
|
|
{
|
|
// whatever happens close the output stream
|
|
if(out != null)
|
|
{
|
|
out.close();
|
|
out = null;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}, true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
fgLogger.debug("Failed to copy dstPath:" + dstPath , e);
|
|
|
|
// throw first exception - this is the root of the problem.
|
|
throw new AVMException("Failed to copy filename:" + dstPath, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean isStale(AVMNodeDescriptor avmRef)
|
|
{
|
|
// note: currently specific to WCM use-cases, eg. ETHREEOH-2758
|
|
if ((avmRef.isLayeredDirectory() && avmRef.isPrimary()) || avmRef.isLayeredFile())
|
|
{
|
|
AVMNodeDescriptor srcNode = avmRef;
|
|
|
|
while ((srcNode.isLayeredDirectory() && srcNode.isPrimary()) || srcNode.isLayeredFile())
|
|
{
|
|
AVMNodeDescriptor targetNode = fAVMService.lookup(srcNode.getIndirectionVersion(), srcNode.getIndirection());
|
|
if (targetNode == null)
|
|
{
|
|
if (srcNode.isLayeredFile() ||
|
|
(srcNode.isLayeredDirectory() &&
|
|
(! srcNode.getOpacity()) &&
|
|
fAVMService.getDirectoryListingDirect(srcNode, false).isEmpty()))
|
|
{
|
|
// The target node is missing
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// unbacked layered dir - however opaque or not directly empty
|
|
return false;
|
|
}
|
|
}
|
|
srcNode = targetNode;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
public void setTargetLockRefreshTime(long targetLockRefreshTime)
|
|
{
|
|
this.targetLockRefreshTime = targetLockRefreshTime;
|
|
}
|
|
|
|
/**
|
|
* How long to keep a lock before refreshing it?
|
|
* <p>
|
|
* Short time-out, typically a minute.
|
|
* @return the time in mS for how long to keep the lock.
|
|
*/
|
|
public long getTargetLockRefreshTime()
|
|
{
|
|
return targetLockRefreshTime;
|
|
}
|
|
}
|