diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml
index 073475f43e..7bbe04d743 100644
--- a/config/alfresco/action-services-context.xml
+++ b/config/alfresco/action-services-context.xml
@@ -94,7 +94,10 @@
-
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/bootstrap/downloadsSpace.xml b/config/alfresco/bootstrap/downloadsSpace.xml
new file mode 100644
index 0000000000..9d8449f5eb
--- /dev/null
+++ b/config/alfresco/bootstrap/downloadsSpace.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ workspace
+ SpacesStore
+ downloads_container
+ ${spaces.downloads.root.name}
+ ${spaces.downloads.root.name}
+ ${spaces.downloads.root.description}
+
+
+
+
+
+
+ GROUP_EVERYONE
+ AddChildren
+
+
+ ROLE_OWNER
+ FullControl
+
+
+
+
+
+
diff --git a/config/alfresco/download-services-context.xml b/config/alfresco/download-services-context.xml
new file mode 100644
index 0000000000..c413175b2e
--- /dev/null
+++ b/config/alfresco/download-services-context.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+ alfresco/model/downloadModel.xml
+
+
+
+
+
+
+
+
+
+
+
+ getDownloadStatus
+
+
+
+
+
+
+
+
+
+
+ cancelDownload
+ createDownload
+ deleteDownloads
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.download.DownloadService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.repo.download.DownloadsCleanupJob
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${download.cleaner.startDelayMins}
+
+
+ ${download.cleaner.repeatIntervalMins}
+
+
+
diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml
index a75d8c701e..b7d31a23a0 100644
--- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml
+++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml
@@ -95,6 +95,9 @@ Inbound settings from iBatis
+
+
+
@@ -222,6 +225,7 @@ Inbound settings from iBatis
+
diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-downloads-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-downloads-common-SqlMap.xml
new file mode 100644
index 0000000000..7b00d138cf
--- /dev/null
+++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-downloads-common-SqlMap.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml
index 477ab399a3..8ed9cadca5 100644
--- a/config/alfresco/import-export-context.xml
+++ b/config/alfresco/import-export-context.xml
@@ -407,6 +407,7 @@
${spaces.nodetemplates.childname}
${system.remote_credentials_container.childname}
${system.syncset_definition_container.childname}
+ ${system.downloads_container.childname}
@@ -699,6 +700,11 @@
alfresco/messages/bootstrap-spaces
+
+ /${system.system_container.childname}
+ alfresco/bootstrap/downloadsSpace.xml
+ alfresco/messages/bootstrap-spaces
+
diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties
index 0a94e53356..c6b7eee3db 100644
--- a/config/alfresco/messages/bootstrap-spaces.properties
+++ b/config/alfresco/messages/bootstrap-spaces.properties
@@ -170,3 +170,6 @@ spaces.templates.email.workflowNotification.description=Workflow notification em
spaces.nodeTemplatesSpace.name=Node Templates
spaces.nodeTemplatesSpace.description=Template Nodes for Share - Create New document
+
+spaces.downloads.root.name=Downloads
+spaces.downloads.root.description=Root folder for downloads
diff --git a/config/alfresco/messages/download-model.properties b/config/alfresco/messages/download-model.properties
new file mode 100644
index 0000000000..7cf7250c3b
--- /dev/null
+++ b/config/alfresco/messages/download-model.properties
@@ -0,0 +1 @@
+# Download related messages
diff --git a/config/alfresco/model/downloadModel.xml b/config/alfresco/model/downloadModel.xml
new file mode 100644
index 0000000000..3f3d41a20a
--- /dev/null
+++ b/config/alfresco/model/downloadModel.xml
@@ -0,0 +1,107 @@
+
+
+
+
+ Alfresco Download Model
+ Alfresco
+ 2012-07-31
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+ cm:content
+ false
+
+
+
+ d:boolean
+ true
+ true
+
+
+
+ d:text
+ true
+ PENDING
+
+
+
+
+ PENDING
+ IN_PROGRESS
+ DONE
+ MAX_CONTENT_SIZE_EXCEEDED
+ CANCELLED
+
+
+
+
+
+
+
+ d:int
+ true
+ 0
+
+
+
+ d:long
+ true
+ 0
+
+
+
+ d:long
+ true
+ 0
+
+
+
+ d:long
+ true
+ 0
+
+
+
+ d:long
+ true
+ 0
+
+
+
+ d:boolean
+ true
+ false
+
+
+
+
+
+
+ false
+ true
+
+
+ cm:cmobject
+ true
+ true
+
+
+
+
+ cm:auditable
+
+
+
+
diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml
index 1e74305a36..a308835f2d 100644
--- a/config/alfresco/public-services-security-context.xml
+++ b/config/alfresco/public-services-security-context.xml
@@ -1019,7 +1019,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.download.DownloadService.deleteDownloads=ACL_ALLOW
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index 210683ae24..688e7f081d 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -1,4 +1,3 @@
-
# Repository configuration
repository.name=Main Repository
@@ -517,6 +516,9 @@ system.remote_credentials_container.childname=sys:remote_credentials
# Folder for storing syncset definitions
system.syncset_definition_container.childname=sys:syncset_definitions
+# Folder for storing download archives
+system.downloads_container.childname=sys:downloads
+
# Are user names case sensitive?
user.name.caseSensitive=false
domain.name.caseSensitive=false
@@ -665,7 +667,6 @@ content.transformer.OpenOffice.mimeTypeLimits.ppam.pdf.maxSourceSizeKBytes=4096
content.transformer.OpenOffice.mimeTypeLimits.sldx.pdf.maxSourceSizeKBytes=4096
content.transformer.OpenOffice.mimeTypeLimits.sldm.pdf.maxSourceSizeKBytes=4096
content.transformer.OpenOffice.mimeTypeLimits.vsd.pdf.maxSourceSizeKBytes=4096
-content.transformer.OpenOffice.mimeTypeLimits.xls.pdf.maxSourceSizeKBytes=10240
content.transformer.OpenOffice.mimeTypeLimits.xlsx.pdf.maxSourceSizeKBytes=1536
content.transformer.OpenOffice.mimeTypeLimits.xltx.pdf.maxSourceSizeKBytes=1536
content.transformer.OpenOffice.mimeTypeLimits.xlsm.pdf.maxSourceSizeKBytes=1536
@@ -1018,5 +1019,17 @@ ticket.cleanup.cronExpression=0 0 * * * ?
#
sample.site.disabled=false
+#
+# Download Service Cleanup
+#
+download.cleaner.startDelayMins=60
+download.cleaner.repeatIntervalMins=60
+download.cleaner.maxAgeMins=60
+
# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden
-system.quickshare.enabled=true
\ No newline at end of file
+system.quickshare.enabled=true
+
+#
+# Download Service Limits, in bytes
+#
+download.maxContentSize=2152852358
diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java
index 0c5c3350c9..b5dc17c2c9 100644
--- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java
+++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java
@@ -108,6 +108,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
private AuthenticationContext authenticationContext;
private ActionTrackingService actionTrackingService;
private PolicyComponent policyComponent;
+ private ActionServiceMonitor monitor;
/**
* The asynchronous action execution queues map of name, queue
@@ -201,6 +202,14 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
{
this.policyComponent = policyComponent;
}
+
+ /**
+ * @param monitor used to monitor running actions and execution times
+ */
+ public void setMonitor(ActionServiceMonitor monitor)
+ {
+ this.monitor = monitor;
+ }
/**
* Set the asynchronous action execution queues
@@ -706,8 +715,22 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
actionTrackingService.recordActionExecuting(action);
}
- // Execute the action
- directActionExecution(action, actionedUponNodeRef);
+ RunningAction runningAction = monitor.actionStarted(action);
+
+ try
+ {
+ // Execute the action
+ directActionExecution(action, actionedUponNodeRef);
+ }
+ catch (Throwable e)
+ {
+ runningAction.setException(e);
+ throw e;
+ }
+ finally
+ {
+ monitor.actionCompleted(runningAction);
+ }
if (getTrackStatus(action))
{
diff --git a/source/java/org/alfresco/repo/action/ActionServiceMonitor.java b/source/java/org/alfresco/repo/action/ActionServiceMonitor.java
new file mode 100644
index 0000000000..e2eefc5ce9
--- /dev/null
+++ b/source/java/org/alfresco/repo/action/ActionServiceMonitor.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2005-2011 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 .
+ */
+package org.alfresco.repo.action;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.alfresco.service.cmr.action.Action;
+
+/**
+ * Responsible for monitoring running actions and accumulating statistics on actions that have been run.
+ *
+ * @author Alex Miller
+ */
+public class ActionServiceMonitor
+{
+ private ConcurrentHashMap runningActions = new ConcurrentHashMap();
+ private ConcurrentHashMap actionStatistics = new ConcurrentHashMap();
+
+ /**
+ * Called by the {@link ActionServiceImpl} when an action is started.
+ *
+ * Adds the action to the list of currently running actions.
+ *
+ * @param action The action being started
+ * @return A {@link RunningAction} object used to track the status of the running action.
+ */
+ public RunningAction actionStarted(Action action)
+ {
+ RunningAction runningAction = new RunningAction(action);
+
+ this.runningActions.put(runningAction.getId(), runningAction);
+
+ return runningAction;
+ }
+
+ /**
+ * Called by the {@link ActionServiceImpl} when sn action completes.
+ *
+ * Removes the actions from the list of currently running actions, and updated the accumulated statistics for that action.
+ *
+ * @param action The {@link RunningAction} object returned by actionStatred.
+ */
+ public void actionCompleted(RunningAction action)
+ {
+ runningActions.remove(action.getId());
+ updateActionStatisitcis(action);
+ }
+
+ private void updateActionStatisitcis(RunningAction action)
+ {
+ String actionName = action.getActionName();
+ ActionStatistics actionStats = actionStatistics.get(actionName);
+ if (actionStats == null)
+ {
+ actionStatistics.putIfAbsent(actionName, new ActionStatistics(actionName));
+ actionStats = actionStatistics.get(actionName);
+ }
+
+ actionStats.addAction(action);
+ }
+
+ /**
+ * @return The list of currently running actions.
+ */
+ public List getRunningActions()
+ {
+ return Collections.unmodifiableList(new ArrayList(runningActions.values()));
+ }
+
+ /**
+ * @return a count of the currently running actions
+ */
+ public int getRunningActionCount()
+ {
+ return runningActions.size();
+ }
+
+ /**
+ * @return a list of the accumulated action statistics.
+ */
+ public List getActionStatisitcs()
+ {
+ return Collections.unmodifiableList(new ArrayList(actionStatistics.values()));
+ }
+}
diff --git a/source/java/org/alfresco/repo/action/ActionStatistics.java b/source/java/org/alfresco/repo/action/ActionStatistics.java
new file mode 100644
index 0000000000..f26e511c23
--- /dev/null
+++ b/source/java/org/alfresco/repo/action/ActionStatistics.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2005-2011 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 .
+ */
+package org.alfresco.repo.action;
+
+/**
+ * Responsible for accumulating and providing statistics on the invocations of a particualr action.
+ *
+ * @author Alex Miller
+ */
+public class ActionStatistics
+{
+ private String actionName;
+
+ long invocationCount = 0;
+ long errorCount = 0;
+ long totalTime = 0;
+
+ /**
+ * @param actionName The name of the action this object will provide statistics for.
+ */
+ public ActionStatistics(String actionName)
+ {
+ this.actionName = actionName;
+ }
+
+ /**
+ * Accumulate statistics from action.
+ */
+ public synchronized void addAction(RunningAction action)
+ {
+ invocationCount = invocationCount + 1;
+ if (action.hasError() == true)
+ {
+ errorCount = errorCount +1;
+ }
+ totalTime = totalTime + action.getElapsedTime();
+ }
+
+ /**
+ * @return The name of the actions this object has statistics for
+ */
+ public String getActionName()
+ {
+ return actionName;
+ }
+
+ /**
+ * @return The number of times the action has been invoked
+ */
+ public long getInvocationCount()
+ {
+ return invocationCount;
+ }
+
+ /**
+ * @return The number of time the invocation of this action has resulted in an exception
+ */
+ public long getErrorCount()
+ {
+ return errorCount;
+ }
+
+ /**
+ * @return The average time for the invocation of this action
+ */
+ public long getAverageTime()
+ {
+ return totalTime / invocationCount;
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/action/RunningAction.java b/source/java/org/alfresco/repo/action/RunningAction.java
new file mode 100644
index 0000000000..88011662ec
--- /dev/null
+++ b/source/java/org/alfresco/repo/action/RunningAction.java
@@ -0,0 +1,85 @@
+package org.alfresco.repo.action;
+
+import java.util.Date;
+import java.util.UUID;
+
+import org.alfresco.service.cmr.action.Action;
+
+/**
+ * Responsible for tracking the invocation of an action.
+ *
+ * @author Alex Miller
+ */
+public class RunningAction
+{
+ private UUID id = UUID.randomUUID();
+
+ private String name;
+ private Thread thread;
+
+ private Date started;
+
+ private boolean exceptionThrown = false;
+
+ /**
+ * @param action The action being run
+ */
+ public RunningAction(Action action)
+ {
+ this.name = action.getActionDefinitionName();
+ this.started = new Date();
+ this.thread = Thread.currentThread();
+ }
+
+
+ /**
+ * @return The name of the action this object is tracking
+ */
+ public String getActionName()
+ {
+ return name;
+ }
+
+ /**
+ * @return The name of thread the action is being run on
+ */
+ public String getThread()
+ {
+ return thread.toString();
+ }
+
+
+ /**
+ * @return The generated id for the action invocation
+ */
+ public UUID getId()
+ {
+ return id;
+ }
+
+ /**
+ * @return The time since the action was started
+ */
+ public long getElapsedTime()
+ {
+ return System.currentTimeMillis() - started.getTime();
+ }
+
+
+ /**
+ * Called by the {@link ActionServiceImpl} if the action generates an exception during invocation.
+ */
+ public void setException(Throwable e)
+ {
+ this.exceptionThrown = true;
+ }
+
+
+ /**
+ * @return true, if setException was called
+ */
+ public boolean hasError()
+ {
+ return exceptionThrown;
+ }
+}
diff --git a/source/java/org/alfresco/repo/download/AbstractExporter.java b/source/java/org/alfresco/repo/download/AbstractExporter.java
new file mode 100644
index 0000000000..24b0d151d8
--- /dev/null
+++ b/source/java/org/alfresco/repo/download/AbstractExporter.java
@@ -0,0 +1,167 @@
+package org.alfresco.repo.download;
+
+import java.io.InputStream;
+import java.util.Locale;
+
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.AccessPermission;
+import org.alfresco.service.cmr.view.Exporter;
+import org.alfresco.service.cmr.view.ExporterContext;
+import org.alfresco.service.namespace.QName;
+
+public class AbstractExporter implements Exporter
+{
+
+ @Override
+ public void start(ExporterContext context)
+ {
+ }
+
+ @Override
+ public void startNamespace(String prefix, String uri)
+ {
+ }
+
+ @Override
+ public void endNamespace(String prefix)
+ {
+ }
+
+ @Override
+ public void startNode(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void endNode(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startReference(NodeRef nodeRef, QName childName)
+ {
+ }
+
+ @Override
+ public void endReference(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startAspects(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startAspect(NodeRef nodeRef, QName aspect)
+ {
+ }
+
+ @Override
+ public void endAspect(NodeRef nodeRef, QName aspect)
+ {
+ }
+
+ @Override
+ public void endAspects(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startACL(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void permission(NodeRef nodeRef, AccessPermission permission)
+ {
+ }
+
+ @Override
+ public void endACL(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startProperties(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startProperty(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void endProperty(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void endProperties(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startValueCollection(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void startValueMLText(NodeRef nodeRef, Locale locale)
+ {
+ }
+
+ @Override
+ public void endValueMLText(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void value(NodeRef nodeRef, QName property, Object value, int index)
+ {
+ }
+
+ @Override
+ public void content(NodeRef nodeRef, QName property, InputStream content,
+ ContentData contentData, int index)
+ {
+ }
+
+ @Override
+ public void endValueCollection(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void startAssocs(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startAssoc(NodeRef nodeRef, QName assoc)
+ {
+ }
+
+ @Override
+ public void endAssoc(NodeRef nodeRef, QName assoc)
+ {
+ }
+
+ @Override
+ public void endAssocs(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void warning(String warning)
+ {
+ }
+
+ @Override
+ public void end()
+ {
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/download/ActionServiceHelper.java b/source/java/org/alfresco/repo/download/ActionServiceHelper.java
new file mode 100644
index 0000000000..42e896845c
--- /dev/null
+++ b/source/java/org/alfresco/repo/download/ActionServiceHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2011 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 .
+ */
+package org.alfresco.repo.download;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * ActionServiceHelper interface.
+ *
+ * Allows the download service to switch between executing the zip creation process in the current alfresco node,
+ * or on a remote node.
+ *
+ * @author Alex Miller
+ */
+public interface ActionServiceHelper
+{
+
+ /**
+ * Implementations should trigger the CreateDownloadArchiveAction on the provided downloadNode
+ *
+ * @param downloadNode
+ */
+ void executeAction(NodeRef downloadNode);
+
+}
diff --git a/source/java/org/alfresco/repo/download/BaseExporter.java b/source/java/org/alfresco/repo/download/BaseExporter.java
new file mode 100644
index 0000000000..96fd5bfdb5
--- /dev/null
+++ b/source/java/org/alfresco/repo/download/BaseExporter.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2005-2012 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 .
+ */
+package org.alfresco.repo.download;
+
+import java.io.InputStream;
+import java.util.Locale;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.coci.CheckOutCheckInService;
+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.security.AccessPermission;
+import org.alfresco.service.cmr.view.Exporter;
+import org.alfresco.service.cmr.view.ExporterContext;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Base {@link Exporter} providing a default implementation of all methods.
+ *
+ * @author Alex Miller
+ */
+abstract class BaseExporter implements Exporter
+{
+ private CheckOutCheckInService checkOutCheckInService;
+ protected NodeService nodeService;
+
+ BaseExporter(CheckOutCheckInService checkOutCheckInService, NodeService nodeService)
+ {
+ this.checkOutCheckInService = checkOutCheckInService;
+ this.nodeService = nodeService;
+ }
+
+ @Override
+ public void start(ExporterContext context)
+ {
+ }
+
+ @Override
+ public void startNamespace(String prefix, String uri)
+ {
+ }
+
+ @Override
+ public void endNamespace(String prefix)
+ {
+ }
+
+ @Override
+ public void startNode(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void endNode(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startReference(NodeRef nodeRef, QName childName)
+ {
+ }
+
+ @Override
+ public void endReference(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startAspects(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startAspect(NodeRef nodeRef, QName aspect)
+ {
+ }
+
+ @Override
+ public void endAspect(NodeRef nodeRef, QName aspect)
+ {
+ }
+
+ @Override
+ public void endAspects(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startACL(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void permission(NodeRef nodeRef, AccessPermission permission)
+ {
+ }
+
+ @Override
+ public void endACL(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startProperties(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startProperty(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void endProperty(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void endProperties(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startValueCollection(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void startValueMLText(NodeRef nodeRef, Locale locale)
+ {
+ }
+
+ @Override
+ public void endValueMLText(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void value(NodeRef nodeRef, QName property, Object value, int index)
+ {
+ }
+
+ @Override
+ public void content(NodeRef nodeRef, QName property, InputStream content, ContentData contentData, int index)
+ {
+ if (checkOutCheckInService.isCheckedOut(nodeRef) == true)
+ {
+ String owner = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_OWNER);
+ if (AuthenticationUtil.getRunAsUser().equals(owner) == true)
+ {
+ return;
+ }
+ }
+
+ if (checkOutCheckInService.isWorkingCopy(nodeRef) == true)
+ {
+ String owner = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_WORKING_COPY_OWNER);
+ if (AuthenticationUtil.getRunAsUser().equals(owner) == false)
+ {
+ return;
+ }
+ }
+
+ contentImpl(nodeRef, property, content, contentData, index);
+ }
+
+ /**
+ * Template method for actually dealing with the content.
+ *
+ * Called by the content method, after filtering for working copies.
+ *
+ */
+ protected abstract void contentImpl(NodeRef nodeRef, QName property, InputStream content, ContentData contentData, int index);
+
+ @Override
+ public void endValueCollection(NodeRef nodeRef, QName property)
+ {
+ }
+
+ @Override
+ public void startAssocs(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void startAssoc(NodeRef nodeRef, QName assoc)
+ {
+ }
+
+ @Override
+ public void endAssoc(NodeRef nodeRef, QName assoc)
+ {
+ }
+
+ @Override
+ public void endAssocs(NodeRef nodeRef)
+ {
+ }
+
+ @Override
+ public void warning(String warning)
+ {
+ }
+
+ @Override
+ public void end()
+ {
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/download/ContentServiceHelper.java b/source/java/org/alfresco/repo/download/ContentServiceHelper.java
new file mode 100644
index 0000000000..237bfce4d9
--- /dev/null
+++ b/source/java/org/alfresco/repo/download/ContentServiceHelper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2005-2011 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 .
+ */
+package org.alfresco.repo.download;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.alfresco.service.cmr.repository.ContentIOException;
+import org.alfresco.service.cmr.repository.NodeRef;
+
+/**
+ * ContentServiceHelper interface.
+ *
+ * Allows us to switch between the zip creation process updating content using a local content service
+ * and updating the content through a remote alfresco node.
+ *
+ * @author amiller
+ */
+public interface ContentServiceHelper
+{
+ /**
+ * Implementations should update the content of downlaodNode with contents of archiveFile.
+ *
+ * @param downloadNode
+ * @param archiveFile
+ * @throws ContentIOException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void updateContent(NodeRef downloadNode, File archiveFile) throws ContentIOException, FileNotFoundException, IOException;
+}
diff --git a/source/java/org/alfresco/repo/download/CreateDownloadArchiveAction.java b/source/java/org/alfresco/repo/download/CreateDownloadArchiveAction.java
new file mode 100644
index 0000000000..45d8352766
--- /dev/null
+++ b/source/java/org/alfresco/repo/download/CreateDownloadArchiveAction.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2005-2012 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 .
+ */
+package org.alfresco.repo.download;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.RenditionModel;
+import org.alfresco.repo.action.executer.ActionExecuter;
+import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ParameterDefinition;
+import org.alfresco.service.cmr.coci.CheckOutCheckInService;
+import org.alfresco.service.cmr.download.DownloadRequest;
+import org.alfresco.service.cmr.download.DownloadStatus;
+import org.alfresco.service.cmr.download.DownloadStatus.Status;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.ContentIOException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.view.ExporterCrawlerParameters;
+import org.alfresco.service.cmr.view.ExporterService;
+import org.alfresco.service.cmr.view.Location;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.TempFileProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ActionExecuter} for creating an archive (ie. zip) file containing
+ * content from the repository.
+ *
+ * The maximum total size of the content which can be downloaded is controlled
+ * by the maximumContentSie property. -1 indicates no limit.
+ *
+ * @author Alex Miller
+ */
+public class CreateDownloadArchiveAction extends ActionExecuterAbstractBase
+{
+ private static final Logger log = LoggerFactory.getLogger(CreateDownloadArchiveAction.class);
+
+
+ private static final String CREATION_ERROR = "Unexpected error creating archive file for download";
+ private static final String TEMP_FILE_PREFIX = "download";
+ private static final String TEMP_FILE_SUFFIX = ".zip";
+
+ // Dependencies
+ private CheckOutCheckInService checkOutCheckInService;
+ private ContentServiceHelper contentServiceHelper;
+ private DownloadStorage downloadStorage;
+ private ExporterService exporterService;
+ private NodeService nodeService;
+ private RetryingTransactionHelper transactionHelper;
+ private DownloadStatusUpdateService updateService;
+
+ private long maximumContentSize = -1l;
+
+ private static class SizeEstimator extends BaseExporter
+ {
+ /**
+ * @param checkOutCheckInService
+ * @param nodeService
+ */
+ SizeEstimator(CheckOutCheckInService checkOutCheckInService, NodeService nodeService)
+ {
+ super(checkOutCheckInService, nodeService);
+ }
+
+ private long size = 0;
+ private long fileCount = 0;
+
+
+ @Override
+ protected void contentImpl(NodeRef nodeRef, QName property, InputStream content, ContentData contentData, int index)
+ {
+ size = size + contentData.getSize();
+ fileCount = fileCount + 1;
+ }
+
+ public long getSize()
+ {
+ return size;
+ }
+
+ public long getFileCount()
+ {
+ return fileCount;
+ }
+
+ }
+
+ // Dependency setters
+ public void setCheckOutCheckInSerivce(CheckOutCheckInService checkOutCheckInService)
+ {
+ this.checkOutCheckInService = checkOutCheckInService;
+ }
+
+
+ public void setContentServiceHelper(ContentServiceHelper contentServiceHelper)
+ {
+ this.contentServiceHelper = contentServiceHelper;
+ }
+
+ public void setDownloadStorage(DownloadStorage downloadStorage)
+ {
+ this.downloadStorage = downloadStorage;
+ }
+
+ public void setExporterService(ExporterService exporterService)
+ {
+ this.exporterService = exporterService;
+ }
+
+ /**
+ * Set the maximum total size of content that can be added to a single
+ * download. -1 indicates no limit.
+ */
+ public void setMaximumContentSize(long maximumContentSize)
+ {
+ this.maximumContentSize = maximumContentSize;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setTransactionHelper(RetryingTransactionHelper transactionHelper)
+ {
+ this.transactionHelper = transactionHelper;
+ }
+
+ public void setUpdateService(DownloadStatusUpdateService updateService)
+ {
+ this.updateService = updateService;
+ }
+
+ /**
+ * Create an archive file containing content from the repository.
+ *
+ * Uses the {@link ExporterService} with custom exporters to create the
+ * archive files.
+ *
+ * @param actionedUponNodeRef Download node containing information required
+ * to create the archive file, and which will eventually have its content
+ * updated with the archive file.
+ */
+ @Override
+ protected void executeImpl(Action action, final NodeRef actionedUponNodeRef)
+ {
+ // Get the download request data and set up the exporter crawler parameters.
+ final DownloadRequest downloadRequest = downloadStorage.getDownloadRequest(actionedUponNodeRef);
+
+ AuthenticationUtil.runAs(new RunAsWork