Merge from BRANCHES/DEV/CLOUD1_SPRINT1 to HEAD:

40238: CLOUD-37 - Initial Commit to test
        Merged BRANCHES/DEV/AMILLER/CLOUD1_SPRINT1 to BRANCHES/DEV/CLOUD1_SPRINT1:
           40077: CLOUD-37: Initial commit.
           40101: CLOUD-37: Fix build error.
           40114: CLOUD-37: Fix path names and missing files.
           40122: CLOUD-37: Initial drop of UI code for investigation of progress issues
           40124: CLOUD-37: A couple of minor UI tweaks (set icon and hide panel before archive download)
           40125: CLOUD-37: Download files and folders as zip
           40134: CLOUD-37: Updates to UI (javascript doc, CSS tweaks, intervals for requests, labels, etc).
           40143: CLOUD-37: Error messages for failures, more JavaScript doc, archive naming, code tidy   40157: CLOUD-37 - Download files and folders as zip
           40202: CLOUD-37: UI tweaks following UX review
           40217: CLOUD-37: Add file count to status reports.
           40222: CLOUD-37: Added information to download dialog to report on the number of files added to the zip 
   40240: CLOUD-37: Remove extraneous file, breaking build
   40513: CLOUD-37: Add Action Service Metrics
        Merged BRANCHES/DEV/AMILLER/CLOUD1_SPRINT1 to BRANCHES/DEV/CLOUD1_SPRINT1:
           40260: CLOUD-37: Add action service metrics
           40309: CLOUD-37: Fix JMX configuration, pointing at renamed class.
   40514: CLOUD-37: Enable the execution of the zip creation process on a remote transformation node
        Merged BRANCHES/DEV/AMILLER/CLOUD1_SPRINT1 to BRANCHES/DEV/CLOUD1_SPRINT1:
           40369: CLOUD-37: Enable the execution of the zip creation process on a remote transformation node   
   40516: CLOUD-37: Implement clean up job.
        Merged BRANCHES/DEV/AMILLER/CLOUD1_SPRINT1 to BRANCHES/DEV/CLOUD1_SPRINT1:
           40462: CLOUD-37: Implement clean up job.
   40517: CLOUD-505: Add entries for folders.
        Merged BRANCHES/DEV/AMILLER/CLOUD1_SPRINT1 to BRANCHES/DEV/CLOUD1_SPRINT1:
           40493: CLOUD-505: Add entries for folders.
   40547: CLOUD-37: Fix broken test
   40595: CLOUD-518: Add working copy/locked file filtering
   40642: CLOUD-508: Prevent problems occurring when cancelling and restarting the same download
   40643: CLOUD-507: When a single item is selected for download it the item name gets used for the archive name
   41442: CLOUD-590: Limit the total size of the content which can be downloaded. This can be set via the property, download.maxContentSize. The default is 2GB.
   41472: CLOUD-589: Added cancelled flag to download type and added checks in Zip creation action to act upon the setting of this flag. Also added webscript for canceling the download.
   41692: Adds support to Alfresco.util.formatFileSize for file sizes with commas (as needed by zip download)
   41693: Zip Download enhancements:
       CLOUD-590: Notifies the user when they've exceeded the maximum file size limit.
       CLOUD-626: Better handling when there are errors during zipping. (WIP)
   41713: Zip Download Updates:
        CLOUD-589: A cancel download UI action now triggers a delete of the archive on the server.
        CLOUD-626: The UI now triggers a full download cancel (with node delete) in event of an error.
   41737: Updates Alfresco.util.formatFileSize to support an optional decimal places param. (For CLOUD-685)
   41739: CLOUD-685: Display total file size of files for download to two decimal places when there is an error.
   41832: Fixes: CLOUD-704: new CANCELLED status is now handled correctly.
   41887: CLOUD-686: Updated maximum download content size to 2152852358 bytes (2.005GB)
   41965: CLOUD-703: Upload content now runs as system user, and Quota Service returns unlimited quota for system user.
   42025: CLOUD-703: Fix test failures and ensure S3 content store works in the clustered and non-clustered environments

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@42146 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Draper
2012-09-28 13:26:36 +00:00
parent ed48e2c4c9
commit 642d332d24
43 changed files with 3561 additions and 6 deletions

View File

@@ -95,6 +95,9 @@
<property name="dictionaryService"> <property name="dictionaryService">
<ref bean="DictionaryService" /> <ref bean="DictionaryService" />
</property> </property>
<property name="monitor">
<ref bean="actionServiceMonitor"/>
</property>
<property name="asynchronousActionExecutionQueues"> <property name="asynchronousActionExecutionQueues">
<map> <map>
@@ -125,6 +128,9 @@
</property> </property>
</bean> </bean>
<!-- Running Actions monitor -->
<bean id="actionServiceMonitor" class="org.alfresco.repo.action.ActionServiceMonitor"/>
<!-- JavaScript API support for action tracking --> <!-- JavaScript API support for action tracking -->
<bean id="actionTrackingServiceScript" parent="baseJavaScriptExtension" <bean id="actionTrackingServiceScript" parent="baseJavaScriptExtension"
class="org.alfresco.repo.action.script.ScriptActionTrackingService"> class="org.alfresco.repo.action.script.ScriptActionTrackingService">

View File

@@ -13,6 +13,7 @@
<import resource="classpath:alfresco/calendar-services-context.xml"/> <import resource="classpath:alfresco/calendar-services-context.xml"/>
<import resource="classpath:alfresco/comment-services-context.xml"/> <import resource="classpath:alfresco/comment-services-context.xml"/>
<import resource="classpath:alfresco/discussions-services-context.xml"/> <import resource="classpath:alfresco/discussions-services-context.xml"/>
<import resource="classpath:alfresco/download-services-context.xml"/>
<import resource="classpath:alfresco/links-services-context.xml"/> <import resource="classpath:alfresco/links-services-context.xml"/>
<import resource="classpath:alfresco/quickshare-services-context.xml"/> <import resource="classpath:alfresco/quickshare-services-context.xml"/>
<import resource="classpath:alfresco/rating-services-context.xml"/> <import resource="classpath:alfresco/rating-services-context.xml"/>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<view:view xmlns:view="http://www.alfresco.org/view/repository/1.0"
xmlns:d="http://www.alfresco.org/model/dictionary/1.0" xmlns:cm="http://www.alfresco.org/model/content/1.0"
xmlns:sys="http://www.alfresco.org/model/system/1.0" xmlns="">
<sys:container view:childName="${system.downloads_container.childname}">
<view:properties>
<sys:store-protocol>workspace</sys:store-protocol>
<sys:store-identifier>SpacesStore</sys:store-identifier>
<sys:node-uuid>downloads_container</sys:node-uuid>
<cm:name>${spaces.downloads.root.name}</cm:name>
<cm:title>${spaces.downloads.root.name}</cm:title>
<cm:description>${spaces.downloads.root.description}</cm:description>
</view:properties>
<!-- By default, anyone can add children (new downloads) -->
<!-- but not read, edit or delete other's sync sets -->
<view:acl view:inherit="false">
<view:ace view:access="ALLOWED">
<view:authority>GROUP_EVERYONE</view:authority>
<view:permission>AddChildren</view:permission>
</view:ace>
<view:ace view:access="ALLOWED">
<view:authority>ROLE_OWNER</view:authority>
<view:permission>FullControl</view:permission>
</view:ace>
</view:acl>
</sys:container>
</view:view>

View File

@@ -0,0 +1,141 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- Bootstrap the Sync model -->
<bean id="downloadDictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
<property name="models">
<list>
<value>alfresco/model/downloadModel.xml</value>
</list>
</property>
</bean>
<!-- Transaction Interceptors for the Calendar Service -->
<bean id="downloadServiceReadTxnAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref bean="retryingReadTxnAdvice"/>
</property>
<property name="mappedNames">
<list>
<value>getDownloadStatus</value>
</list>
</property>
</bean>
<bean id="downloadServiceWriteTxnAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref bean="retryingWriteTxnAdvice"/>
</property>
<property name="mappedNames">
<list>
<value>cancelDownload</value>
<value>createDownload</value>
<value>deleteDownloads</value>
</list>
</property>
</bean>
<!-- Download Service -->
<bean id="DownloadService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.alfresco.service.cmr.download.DownloadService</value>
</property>
<property name="target">
<ref bean="downloadService" />
</property>
<property name="interceptorNames">
<list>
<idref bean="downloadServiceReadTxnAdvisor" />
<idref bean="downloadServiceWriteTxnAdvisor" />
<idref bean="checkTxnAdvisor" />
<idref bean="AuditMethodInterceptor" />
<idref bean="exceptionTranslator" />
<idref bean="DownloadService_security" />
</list>
</property>
</bean>
<bean id="downloadCannedQueryRegistry" class="org.alfresco.util.registry.NamedObjectRegistry">
<property name="storageType" value="org.alfresco.query.CannedQueryFactory"/>
</bean>
<bean name="downloadGetDownloadsCannedQueryFactory" class="org.alfresco.repo.download.cannedquery.GetDownloadsCannedQueryFactory">
<property name="registry" ref="downloadCannedQueryRegistry"/>
<property name="nodeDAO" ref="nodeDAO"/>
<property name="qnameDAO" ref="qnameDAO"/>
<property name="cannedQueryDAO" ref="cannedQueryDAO"/>
<property name="tenantService" ref="tenantService"/>
<property name="methodSecurity" ref="DownloadService_security_deleteDownloads"/>
</bean>
<bean id="downloadStorage" class="org.alfresco.repo.download.DownloadStorage">
<property name="importerBootstrap" ref="spacesBootstrap"/>
<property name="repositoryHelper" ref="repositoryHelper"/>
<property name="nodeService" ref="nodeService"/>
<property name="namespaceService" ref="namespaceService"/>
<property name="queryRegistry" ref="downloadCannedQueryRegistry"/>
</bean>
<bean id="downloadStatusUpdateService" class="org.alfresco.repo.download.DownloadStatusUpdateServiceImpl">
<property name="storage" ref="downloadStorage" />
</bean>
<bean id="downloadContentServiceHelper" class="org.alfresco.repo.download.LocalContentServiceHelper">
<property name="contentService" ref="contentService"/>
</bean>
<bean id="createDownloadArchiveAction" class="org.alfresco.repo.download.CreateDownloadArchiveAction" parent="action-executer">
<property name="checkOutCheckInSerivce" ref="checkOutCheckInService"/>
<property name="contentServiceHelper" ref="downloadContentServiceHelper" />
<property name="downloadStorage" ref="downloadStorage" />
<property name="exporterService" ref="exporterComponent" />
<property name="maximumContentSize" value="${download.maxContentSize}" />
<property name="nodeService" ref="nodeService" />
<property name="publicAction" value="false"/>
<property name="transactionHelper" ref="retryingTransactionHelper"/>
<property name="updateService" ref="downloadStatusUpdateService"/>
</bean>
<bean id="downloadActionServiceHelper" class="org.alfresco.repo.download.LocalActionServiceHelper">
<property name="localActionService" ref="actionService"/>
</bean>
<bean id="downloadService" class="org.alfresco.repo.download.DownloadServiceImpl">
<property name="actionServiceHelper" ref="downloadActionServiceHelper"/>
<property name="downloadStorage" ref="downloadStorage"/>
<property name="transactionHelper" ref="retryingTransactionHelper"/>
</bean>
<bean id="downloadCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>org.alfresco.repo.download.DownloadsCleanupJob</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="downloadService" value-ref="DownloadService" />
<entry key="tenantAdminService" value-ref="tenantAdminService"/>
<entry key="maxAgeInMinutes" value="${download.cleaner.maxAgeMins}"/>
</map>
</property>
</bean>
<bean id="downloadCleanerTrigger" class="org.alfresco.util.TriggerBean">
<property name="jobDetail">
<ref bean="downloadCleanerJobDetail" />
</property>
<property name="scheduler">
<ref bean="schedulerFactory" />
</property>
<property name="startDelayMinutes">
<value>${download.cleaner.startDelayMins}</value>
</property>
<property name="repeatIntervalMinutes">
<value>${download.cleaner.repeatIntervalMins}</value>
</property>
</bean>
</beans>

View File

@@ -95,6 +95,9 @@ Inbound settings from iBatis
<!-- Discussions CQ --> <!-- Discussions CQ -->
<typeAlias alias="NodeWithChildrenEntity" type="org.alfresco.repo.discussion.cannedqueries.NodeWithChildrenEntity"/> <typeAlias alias="NodeWithChildrenEntity" type="org.alfresco.repo.discussion.cannedqueries.NodeWithChildrenEntity"/>
<!-- Downloads CQ -->
<typeAlias alias="Download" type="org.alfresco.repo.download.cannedquery.DownloadEntity"/>
<!-- Patch --> <!-- Patch -->
<typeAlias alias="Ids" type="org.alfresco.ibatis.IdsEntity"/> <typeAlias alias="Ids" type="org.alfresco.ibatis.IdsEntity"/>
@@ -222,6 +225,7 @@ Inbound settings from iBatis
<mapper resource="alfresco/ibatis/#resource.dialect#/query-blogs-common-SqlMap.xml"/> <mapper resource="alfresco/ibatis/#resource.dialect#/query-blogs-common-SqlMap.xml"/>
<mapper resource="alfresco/ibatis/#resource.dialect#/query-calendar-common-SqlMap.xml"/> <mapper resource="alfresco/ibatis/#resource.dialect#/query-calendar-common-SqlMap.xml"/>
<mapper resource="alfresco/ibatis/#resource.dialect#/query-copy-common-SqlMap.xml"/> <mapper resource="alfresco/ibatis/#resource.dialect#/query-copy-common-SqlMap.xml"/>
<mapper resource="alfresco/ibatis/#resource.dialect#/query-downloads-common-SqlMap.xml"/>
<mapper resource="alfresco/ibatis/#resource.dialect#/query-discussion-common-SqlMap.xml"/> <mapper resource="alfresco/ibatis/#resource.dialect#/query-discussion-common-SqlMap.xml"/>
</mappers> </mappers>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="alfresco.query.downloads">
<!-- -->
<!-- Result Maps -->
<!-- -->
<resultMap id="result_Download" type="Download">
<id property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<association property="node" resultMap="alfresco.node.result_Node"/>
</resultMap>
<!-- GetDownloads Canned Query (model-specific) -->
<select id="select_GetDownloadsBeforeQuery" parameterType="org.alfresco.repo.download.cannedquery.GetDownloadsCannedQueryParams" resultMap="result_Download">
select
childNode.id as id,
childStore.protocol as protocol,
childStore.identifier as identifier,
childNode.uuid as uuid,
childNode.audit_created as audit_created,
childNode.audit_creator as audit_creator,
childNode.audit_accessed as audit_accessed,
childNode.audit_modified as audit_modified
from
alf_child_assoc assoc
join alf_node childNode on (childNode.id = assoc.child_node_id)
join alf_store childStore on (childStore.id = childNode.store_id)
where
assoc.parent_node_id = #{parentNodeId}
and childNode.type_qname_id = #{contentTypeQNameId}
order by
childNode.audit_modified ASC
</select>
</mapper>

View File

@@ -407,6 +407,7 @@
<prop key="spaces.nodetemplates.childname">${spaces.nodetemplates.childname}</prop> <prop key="spaces.nodetemplates.childname">${spaces.nodetemplates.childname}</prop>
<prop key="system.remote_credentials_container.childname">${system.remote_credentials_container.childname}</prop> <prop key="system.remote_credentials_container.childname">${system.remote_credentials_container.childname}</prop>
<prop key="system.syncset_definition_container.childname">${system.syncset_definition_container.childname}</prop> <prop key="system.syncset_definition_container.childname">${system.syncset_definition_container.childname}</prop>
<prop key="system.downloads_container.childname">${system.downloads_container.childname}</prop>
</props> </props>
</property> </property>
</bean> </bean>
@@ -699,6 +700,11 @@
<prop key="messages">alfresco/messages/bootstrap-spaces</prop> <prop key="messages">alfresco/messages/bootstrap-spaces</prop>
</props> </props>
<props>
<prop key="path">/${system.system_container.childname}</prop>
<prop key="location">alfresco/bootstrap/downloadsSpace.xml</prop>
<prop key="messages">alfresco/messages/bootstrap-spaces</prop>
</props>
</list> </list>
</property> </property>
</bean> </bean>

View File

@@ -170,3 +170,6 @@ spaces.templates.email.workflowNotification.description=Workflow notification em
spaces.nodeTemplatesSpace.name=Node Templates spaces.nodeTemplatesSpace.name=Node Templates
spaces.nodeTemplatesSpace.description=Template Nodes for Share - Create New document spaces.nodeTemplatesSpace.description=Template Nodes for Share - Create New document
spaces.downloads.root.name=Downloads
spaces.downloads.root.description=Root folder for downloads

View File

@@ -0,0 +1 @@
# Download related messages

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- xsi:schemaLocation="http://www.alfresco.org/model/dictionary/1.0 modelSchema.xsd" -->
<model name="download:downloadModel"
xmlns="http://www.alfresco.org/model/dictionary/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<description>Alfresco Download Model</description>
<author>Alfresco</author>
<published>2012-07-31</published>
<version>1.0</version>
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<namespaces>
<namespace uri="http://www.alfresco.org/model/download/1.0" prefix="download"/>
</namespaces>
<types>
<type name="download:download">
<parent>cm:content</parent>
<archive>false</archive>
<properties>
<property name="download:recursive">
<type>d:boolean</type>
<mandatory>true</mandatory>
<default>true</default>
</property>
<property name="download:status">
<type>d:text</type>
<mandatory>true</mandatory>
<default>PENDING</default>
<constraints>
<constraint type="LIST">
<parameter name="allowedValues">
<list>
<value>PENDING</value>
<value>IN_PROGRESS</value>
<value>DONE</value>
<value>MAX_CONTENT_SIZE_EXCEEDED</value>
<value>CANCELLED</value>
</list>
</parameter>
</constraint>
</constraints>
</property>
<property name="download:sequenceNumber">
<type>d:int</type>
<mandatory>true</mandatory>
<default>0</default>
</property>
<property name="download:done">
<type>d:long</type>
<mandatory>true</mandatory>
<default>0</default>
</property>
<property name="download:total">
<type>d:long</type>
<mandatory>true</mandatory>
<default>0</default>
</property>
<property name="download:filesAdded">
<type>d:long</type>
<mandatory>true</mandatory>
<default>0</default>
</property>
<property name="download:totalFiles">
<type>d:long</type>
<mandatory>true</mandatory>
<default>0</default>
</property>
<property name="download:cancelled">
<type>d:boolean</type>
<mandatory>true</mandatory>
<default>false</default>
</property>
</properties>
<associations>
<!-- This association points to each noderef which is a member of the sync set -->
<association name="download:requestedNodes">
<source>
<mandatory>false</mandatory>
<many>true</many>
</source>
<target>
<class>cm:cmobject</class>
<mandatory>true</mandatory>
<many>true</many>
</target>
</association>
</associations>
<mandatory-aspects>
<aspect>cm:auditable</aspect>
</mandatory-aspects>
</type>
</types>
</model>

View File

@@ -1019,6 +1019,31 @@
<property name="methodName" value="listCalendarEntries" /> <property name="methodName" value="listCalendarEntries" />
</bean> </bean>
<!-- ==================== -->
<!-- The Download Service -->
<!-- ==================== -->
<!-- The download service itself does not require any security restrictions, -->
<!-- they are imposed by the node and site services it uses to do its work. -->
<bean id="DownloadService_security" class="org.alfresco.repo.security.permissions.impl.AlwaysProceedMethodInterceptor" />
<!-- The canned queries that the calendar service uses do however need to check -->
<bean id="DownloadService_CannedQuery_security" class="org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
<property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
<property name="objectDefinitionSource">
<value>
org.alfresco.service.cmr.download.DownloadService.deleteDownloads=ACL_ALLOW
</value>
</property>
</bean>
<bean id="DownloadService_security_deleteDownloads" class="org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityBean">
<property name="methodSecurityInterceptor" ref="DownloadService_CannedQuery_security" />
<property name="service" value="org.alfresco.service.cmr.download.DownloadService" />
<property name="methodName" value="deleteDownloads" />
</bean>
<!-- ==================== --> <!-- ==================== -->
<!-- The Links Service --> <!-- The Links Service -->

View File

@@ -1,4 +1,3 @@
# Repository configuration # Repository configuration
repository.name=Main Repository repository.name=Main Repository
@@ -517,6 +516,9 @@ system.remote_credentials_container.childname=sys:remote_credentials
# Folder for storing syncset definitions # Folder for storing syncset definitions
system.syncset_definition_container.childname=sys: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? # Are user names case sensitive?
user.name.caseSensitive=false user.name.caseSensitive=false
domain.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.sldx.pdf.maxSourceSizeKBytes=4096
content.transformer.OpenOffice.mimeTypeLimits.sldm.pdf.maxSourceSizeKBytes=4096 content.transformer.OpenOffice.mimeTypeLimits.sldm.pdf.maxSourceSizeKBytes=4096
content.transformer.OpenOffice.mimeTypeLimits.vsd.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.xlsx.pdf.maxSourceSizeKBytes=1536
content.transformer.OpenOffice.mimeTypeLimits.xltx.pdf.maxSourceSizeKBytes=1536 content.transformer.OpenOffice.mimeTypeLimits.xltx.pdf.maxSourceSizeKBytes=1536
content.transformer.OpenOffice.mimeTypeLimits.xlsm.pdf.maxSourceSizeKBytes=1536 content.transformer.OpenOffice.mimeTypeLimits.xlsm.pdf.maxSourceSizeKBytes=1536
@@ -1018,5 +1019,17 @@ ticket.cleanup.cronExpression=0 0 * * * ?
# #
sample.site.disabled=false 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 # enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden
system.quickshare.enabled=true system.quickshare.enabled=true
#
# Download Service Limits, in bytes
#
download.maxContentSize=2152852358

View File

@@ -108,6 +108,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
private AuthenticationContext authenticationContext; private AuthenticationContext authenticationContext;
private ActionTrackingService actionTrackingService; private ActionTrackingService actionTrackingService;
private PolicyComponent policyComponent; private PolicyComponent policyComponent;
private ActionServiceMonitor monitor;
/** /**
* The asynchronous action execution queues map of name, queue * The asynchronous action execution queues map of name, queue
@@ -202,6 +203,14 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
this.policyComponent = policyComponent; 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 * Set the asynchronous action execution queues
* *
@@ -706,8 +715,22 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
actionTrackingService.recordActionExecuting(action); actionTrackingService.recordActionExecuting(action);
} }
// Execute the action RunningAction runningAction = monitor.actionStarted(action);
directActionExecution(action, actionedUponNodeRef);
try
{
// Execute the action
directActionExecution(action, actionedUponNodeRef);
}
catch (Throwable e)
{
runningAction.setException(e);
throw e;
}
finally
{
monitor.actionCompleted(runningAction);
}
if (getTrackStatus(action)) if (getTrackStatus(action))
{ {

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<UUID, RunningAction> runningActions = new ConcurrentHashMap<UUID, RunningAction>();
private ConcurrentHashMap<String, ActionStatistics> actionStatistics = new ConcurrentHashMap<String, ActionStatistics>();
/**
* 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<RunningAction> getRunningActions()
{
return Collections.unmodifiableList(new ArrayList<RunningAction>(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<ActionStatistics> getActionStatisitcs()
{
return Collections.unmodifiableList(new ArrayList<ActionStatistics>(actionStatistics.values()));
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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()
{
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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()
{
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Object>()
{
@Override
public Object doWork() throws Exception
{
ExporterCrawlerParameters crawlerParameters = new ExporterCrawlerParameters();
Location exportFrom = new Location(downloadRequest.getRequetedNodeRefs());
crawlerParameters.setExportFrom(exportFrom);
crawlerParameters.setCrawlSelf(true);
crawlerParameters.setExcludeChildAssocs(new QName[] {RenditionModel.ASSOC_RENDITION});
crawlerParameters.setExcludeAspects(new QName[] {ContentModel.ASPECT_WORKING_COPY});
// Get an estimate of the size for statuses
SizeEstimator estimator = new SizeEstimator(checkOutCheckInService, nodeService);
exporterService.exportView(estimator, crawlerParameters, null);
if (maximumContentSize > 0 && estimator.getSize() > maximumContentSize)
{
maximumContentSizeExceeded(actionedUponNodeRef, estimator.getSize(), estimator.getFileCount());
}
else
{
createDownload(actionedUponNodeRef, crawlerParameters, estimator);
}
return null;
}
}, downloadRequest.getOwner());
}
@Override
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
{
}
private void maximumContentSizeExceeded(final NodeRef actionedUponNodeRef, final long size, final long fileCount)
{
log.debug("Maximum contentent size ({}), exceeded ({})", maximumContentSize, size);
//Update the content and set the status to done.
transactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@Override
public Object execute() throws Throwable
{
DownloadStatus status = new DownloadStatus(Status.MAX_CONTENT_SIZE_EXCEEDED, maximumContentSize, size, 0, fileCount);
updateService.update(actionedUponNodeRef, status, 1);
return null;
}
}, false, true);
}
private void createDownload(final NodeRef actionedUponNodeRef, ExporterCrawlerParameters crawlerParameters, SizeEstimator estimator)
{
// perform the actual export
final File tempFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
final ZipDownloadExporter handler = new ZipDownloadExporter(tempFile, checkOutCheckInService, nodeService, transactionHelper, updateService, downloadStorage, actionedUponNodeRef, estimator.getSize(), estimator.getFileCount());
try {
exporterService.exportView(handler, crawlerParameters, null);
archiveCreationComplete(actionedUponNodeRef, tempFile, handler);
}
catch (DownloadCancelledException ex)
{
downloadCancelled(actionedUponNodeRef, handler);
}
finally
{
tempFile.delete();
}
}
private void archiveCreationComplete(final NodeRef actionedUponNodeRef, final File tempFile,
final ZipDownloadExporter handler)
{
//Update the content and set the status to done.
transactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@Override
public Object execute() throws Throwable
{
try
{
contentServiceHelper.updateContent(actionedUponNodeRef, tempFile);
DownloadStatus status = new DownloadStatus(Status.DONE, handler.getDone(), handler.getTotal(), handler.getFilesAdded(), handler.getTotalFiles());
updateService.update(actionedUponNodeRef, status, handler.getNextSequenceNumber());
return null;
}
catch (ContentIOException ex)
{
throw new DownloadServiceException(CREATION_ERROR, ex);
}
catch (FileNotFoundException ex)
{
throw new DownloadServiceException(CREATION_ERROR, ex);
}
catch (IOException ex)
{
throw new DownloadServiceException(CREATION_ERROR, ex);
}
}
}, false, true);
}
private void downloadCancelled(final NodeRef actionedUponNodeRef, final ZipDownloadExporter handler)
{
//Update the content and set the status to done.
transactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@Override
public Object execute() throws Throwable
{
DownloadStatus status = new DownloadStatus(Status.CANCELLED, handler.getDone(), handler.getTotal(), handler.getFilesAdded(), handler.getTotalFiles());
updateService.update(actionedUponNodeRef, status, handler.getNextSequenceNumber());
return null;
}
}, false, true);
}
}

View File

@@ -0,0 +1,18 @@
package org.alfresco.repo.download;
import org.alfresco.service.cmr.view.ExporterException;
/**
* Exception thrown by ZipDownloadExporter, if a download is cancelled mid flow.
*
* @author Alex Miller
*/
public class DownloadCancelledException extends ExporterException
{
private static final long serialVersionUID = 4694929866014032096L;
public DownloadCancelledException()
{
super("Download Cancelled");
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import org.alfresco.service.namespace.QName;
/**
* Utility interface for the downloadModel.xml
*
* @author Alex Miller
*/
public interface DownloadModel
{
/** Download Model URI */
static final String DOWNLOAD_MODEL_1_0_URI = "http://www.alfresco.org/model/download/1.0";
/** Type QName */
static final QName TYPE_DOWNLOAD = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "download");
// Property QNames
static final QName PROP_CANCELLED = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "cancelled");
static final QName PROP_DONE = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "done");
static final QName PROP_FILES_ADDED = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "filesAdded");
static final QName PROP_RECURSIVE = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "recursive");
static final QName PROP_SEQUENCE_NUMBER = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "sequenceNumber");
static final QName PROP_STATUS = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "status");
static final QName PROP_TOTAL = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "total");
static final QName PROP_TOTAL_FILES = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "totalFiles");
// Associations
static final QName ASSOC_REQUESTED_NODES = QName.createQName(DOWNLOAD_MODEL_1_0_URI, "requestedNodes");
}

View File

@@ -0,0 +1,37 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* Download Service Exception class
*
* @author Alex Miller
*/
public class DownloadServiceException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = 1826926526215676002L;
public DownloadServiceException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import java.util.Date;
import java.util.List;
import org.alfresco.repo.download.cannedquery.DownloadEntity;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.download.DownloadService;
import org.alfresco.service.cmr.download.DownloadStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.ParameterCheck;
/**
* Implementation of the download service.
*
* Persists the download reqest and then uses a local action service to execute
* the {@link CreateDownloadArchiveAction}.
*
* @author Alex Miller
*/
public class DownloadServiceImpl implements DownloadService {
// Dependencies
private ActionServiceHelper actionServiceHelper;
private DownloadStorage downloadStorage;
private RetryingTransactionHelper transactionHelper;
// Dependency setters
public void setActionServiceHelper(ActionServiceHelper actionServiceHelper)
{
this.actionServiceHelper = actionServiceHelper;
}
public void setTransactionHelper(RetryingTransactionHelper transactionHelper)
{
this.transactionHelper = transactionHelper;
}
public void setDownloadStorage(DownloadStorage downloadStorage)
{
this.downloadStorage = downloadStorage;
}
@Override
public NodeRef createDownload(final NodeRef[] requestedNodes, final boolean recursive) {
ParameterCheck.mandatory("nodeRefs", requestedNodes);
if (requestedNodes.length < 1)
{
throw new IllegalArgumentException("Need at least 1 node ref");
}
// This is done in a new transaction to avoid node not found errors when the zip creation occurs
// on a remote transformation server.
NodeRef downloadNode = transactionHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
//Create a download node
NodeRef downloadNode = downloadStorage.createDownloadNode(recursive);
//Add requested nodes
for (NodeRef node : requestedNodes)
{
downloadStorage.addNodeToDownload(downloadNode, node);
}
return downloadNode;
}
}, false, true);
//Trigger the action.
actionServiceHelper.executeAction(downloadNode);
return downloadNode;
}
@Override
public DownloadStatus getDownloadStatus(NodeRef downloadNode) {
ParameterCheck.mandatory("downloadNode", downloadNode);
return downloadStorage.getDownloadStatus(downloadNode);
}
/*
* @see org.alfresco.service.cmr.download.DownloadService#deleteDownloads(java.util.Date)
*/
@Override
public void deleteDownloads(Date before)
{
List<List<DownloadEntity>> downloadPages = downloadStorage.getDownloadsCreatedBefore(before);
for (List<DownloadEntity> page : downloadPages)
{
for (DownloadEntity download : page)
{
downloadStorage.delete(download.getNodeRef());
}
}
}
/*
* @see org.alfresco.service.cmr.download.DownloadService#cancelDownload(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void cancelDownload(NodeRef downloadNodeRef)
{
ParameterCheck.mandatory("downloadNodeRef", downloadNodeRef);
downloadStorage.cancelDownload(downloadNodeRef);
}
}

View File

@@ -0,0 +1,408 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import net.sf.acegisecurity.Authentication;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.model.Repository;
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.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.download.DownloadService;
import org.alfresco.service.cmr.download.DownloadStatus;
import org.alfresco.service.cmr.download.DownloadStatus.Status;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.test.junitrules.AlfrescoPerson;
import org.alfresco.util.test.junitrules.ApplicationContextInit;
import org.alfresco.util.test.junitrules.TemporaryNodes;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
/**
* Integration test for DownloadServiceImpl
*
* @author Alex Miller
*/
public class DownloadServiceIntegrationTest
{
public static final long MAX_TIME = 5000;
private static final long PAUSE_TIME = 1000;
// Rule to initialize the default Alfresco spring configuration
public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit();
// Rules to create 2 test users.
public static AlfrescoPerson TEST_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "User");
public static AlfrescoPerson TEST_USER2 = new AlfrescoPerson(APP_CONTEXT_INIT, "User 2");
// A rule to manage test nodes reused across all the test methods
public static TemporaryNodes STATIC_TEST_NODES = new TemporaryNodes(APP_CONTEXT_INIT);
// Tie them together in a static Rule Chain
@ClassRule public static RuleChain ruleChain = RuleChain.outerRule(APP_CONTEXT_INIT)
.around(TEST_USER)
.around(STATIC_TEST_NODES);
// A rule to manage test nodes use in each test method
@Rule public TemporaryNodes testNodes = new TemporaryNodes(APP_CONTEXT_INIT);
// Service under test
public static DownloadService DOWNLOAD_SERVICE;
// Various supporting services
private static CheckOutCheckInService CHECK_OUT_CHECK_IN_SERVICE;
private static ContentService CONTENT_SERVICE;
private static NodeService NODE_SERVICE;
private static PermissionService PERMISSION_SERVICE;
private static RetryingTransactionHelper TRANSACTION_HELPER;
// Test Content
private NodeRef rootFolder;
private NodeRef rootFile;
private NodeRef level1Folder1;
private NodeRef level1Folder2;
private Set<String> allEntries;
private NodeRef fileToCheckout;
@BeforeClass public static void init()
{
// Resolve required services
CHECK_OUT_CHECK_IN_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("CheckOutCheckInService", CheckOutCheckInService.class);
CONTENT_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("contentService", ContentService.class);
DOWNLOAD_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("DownloadService", DownloadService.class);
NODE_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("NodeService", NodeService.class);
PERMISSION_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("PermissionService", PermissionService.class);
TRANSACTION_HELPER = APP_CONTEXT_INIT.getApplicationContext().getBean("retryingTransactionHelper", RetryingTransactionHelper.class);
}
/**
* Create the test content
*/
@Before public void createContent()
{
allEntries = new TreeSet<String>();
AuthenticationUtil.setRunAsUserSystem();
Repository repositoryHelper = (Repository) APP_CONTEXT_INIT.getApplicationContext().getBean("repositoryHelper");
NodeRef COMPANY_HOME = repositoryHelper.getCompanyHome();
// Create some static test content
rootFolder = testNodes.createNode(COMPANY_HOME, "rootFolder", ContentModel.TYPE_FOLDER, AuthenticationUtil.getAdminUserName());
allEntries.add("rootFolder/");
rootFile = testNodes.createNodeWithTextContent(COMPANY_HOME, "rootFile.txt", ContentModel.TYPE_CONTENT, AuthenticationUtil.getAdminUserName(), "Root file content");
allEntries.add("rootFile.txt");
testNodes.createNodeWithTextContent(rootFolder, "level1File.txt", ContentModel.TYPE_CONTENT, AuthenticationUtil.getAdminUserName(), "Level 1 file content");
allEntries.add("rootFolder/level1File.txt");
level1Folder1 = testNodes.createNode(rootFolder, "level1Folder1", ContentModel.TYPE_FOLDER, AuthenticationUtil.getAdminUserName());
allEntries.add("rootFolder/level1Folder1/");
level1Folder2 = testNodes.createNode(rootFolder, "level1Folder2", ContentModel.TYPE_FOLDER, AuthenticationUtil.getAdminUserName());
allEntries.add("rootFolder/level1Folder2/");
testNodes.createNode(rootFolder, "level1EmptyFolder", ContentModel.TYPE_FOLDER, AuthenticationUtil.getAdminUserName());
allEntries.add("rootFolder/level1EmptyFolder/");
testNodes.createNodeWithTextContent(level1Folder1, "level2File.txt", ContentModel.TYPE_CONTENT, AuthenticationUtil.getAdminUserName(), "Level 2 file content");
allEntries.add("rootFolder/level1Folder1/level2File.txt");
testNodes.createNodeWithTextContent(level1Folder2, "level2File.txt", ContentModel.TYPE_CONTENT, AuthenticationUtil.getAdminUserName(), "Level 2 file content");
allEntries.add("rootFolder/level1Folder2/level2File.txt");
fileToCheckout = testNodes.createNodeWithTextContent(level1Folder2, "fileToCheckout.txt", ContentModel.TYPE_CONTENT, AuthenticationUtil.getAdminUserName(), "Level 2 file content");
// Add the lock and version aspects to the created node
NODE_SERVICE.addAspect(fileToCheckout, ContentModel.ASPECT_VERSIONABLE, null);
NODE_SERVICE.addAspect(fileToCheckout, ContentModel.ASPECT_LOCKABLE, null);
allEntries.add("rootFolder/level1Folder2/fileToCheckout.txt");
PERMISSION_SERVICE.setPermission(level1Folder2, TEST_USER.getUsername(), PermissionService.ALL_PERMISSIONS, true);
PERMISSION_SERVICE.setPermission(fileToCheckout, TEST_USER.getUsername(), PermissionService.ALL_PERMISSIONS, true);
}
@Test public void createDownload() throws IOException, InterruptedException
{
// Initiate the download
final NodeRef downloadNode = DOWNLOAD_SERVICE.createDownload(new NodeRef[] {rootFile, rootFolder}, true);
Assert.assertNotNull(downloadNode);
testNodes.addNodeRef(downloadNode);
// Validate that the download node has been persisted correctly.
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Object>()
{
@Override
public Object execute() throws Throwable
{
Map<QName, Serializable> properties = NODE_SERVICE.getProperties(downloadNode);
Assert.assertEquals(Boolean.TRUE, properties.get(DownloadModel.PROP_RECURSIVE));
List<AssociationRef> associations = NODE_SERVICE.getTargetAssocs(downloadNode, DownloadModel.ASSOC_REQUESTED_NODES);
for (AssociationRef association : associations)
{
Assert.assertTrue(association.getTargetRef().equals(rootFile) || association.getTargetRef().equals(rootFolder));
}
return null;
}
});
DownloadStatus status = getDownloadStatus(downloadNode);
while (status.getStatus() == Status.PENDING)
{
Thread.sleep(PAUSE_TIME);
status = getDownloadStatus(downloadNode);
}
Assert.assertEquals(5l, status.getTotalFiles());
long elapsedTime = waitForDownload(downloadNode);
Assert.assertTrue("Maximum creation time exceeded!", elapsedTime < MAX_TIME);
// Validate the content.
final Set<String> entryNames = getEntries(downloadNode);
validateEntries(entryNames, allEntries, true);
}
private void validateEntries(final Set<String> entryNames, final Set<String> expectedEntries, boolean onlyExpected)
{
Set<String> copy = new TreeSet<String>(entryNames);
for (String expectedEntry : expectedEntries)
{
Assert.assertTrue("Missing entry:- " + expectedEntry, copy.contains(expectedEntry));
copy.remove(expectedEntry);
}
if (onlyExpected == true)
{
Assert.assertTrue("Unexpected entries", copy.isEmpty());
}
}
private Set<String> getEntries(final NodeRef downloadNode)
{
return TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Set<String>>()
{
@Override
public Set<String> execute() throws Throwable
{
Set<String> entryNames = new TreeSet<String>();
ContentReader reader = CONTENT_SERVICE.getReader(downloadNode, ContentModel.PROP_CONTENT);
ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(reader.getContentInputStream());
try
{
ZipArchiveEntry zipEntry = zipInputStream.getNextZipEntry();
while (zipEntry != null)
{
String name = zipEntry.getName();
entryNames.add(name);
zipEntry = zipInputStream.getNextZipEntry();
}
}
finally
{
zipInputStream.close();
}
return entryNames;
}
});
}
private long waitForDownload(final NodeRef downloadNode) throws InterruptedException
{
long startTime = System.currentTimeMillis();
// Wait for the staus to become done.
DownloadStatus status;
long elapsedTime;
do {
status = getDownloadStatus(downloadNode);
elapsedTime = System.currentTimeMillis() - startTime;
if (status.isComplete() == false)
{
Thread.sleep(PAUSE_TIME);
}
} while (status.isComplete() == false && elapsedTime < MAX_TIME);
return elapsedTime;
}
private DownloadStatus getDownloadStatus(final NodeRef downloadNode)
{
return TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<DownloadStatus>()
{
@Override
public DownloadStatus execute() throws Throwable
{
return DOWNLOAD_SERVICE.getDownloadStatus(downloadNode);
}
});
}
@Test public void deleteBefore() throws InterruptedException
{
NodeRef beforeNodeRef;
NodeRef afterNodeRef;
Date beforeTime;
beforeNodeRef = DOWNLOAD_SERVICE.createDownload(new NodeRef[] {level1Folder1}, true);
testNodes.addNodeRef(beforeNodeRef);
waitForDownload(beforeNodeRef);
beforeTime = new Date();
afterNodeRef = DOWNLOAD_SERVICE.createDownload(new NodeRef[] {level1Folder2}, true);
testNodes.addNodeRef(afterNodeRef);
waitForDownload(afterNodeRef);
DOWNLOAD_SERVICE.deleteDownloads(beforeTime);
Assert.assertFalse(NODE_SERVICE.exists(beforeNodeRef));
Assert.assertTrue(NODE_SERVICE.exists(afterNodeRef));
}
@Test public void cancel() throws InterruptedException
{
// Initiate the download
final NodeRef downloadNode = DOWNLOAD_SERVICE.createDownload(new NodeRef[] {rootFile, rootFolder}, true);
Assert.assertNotNull(downloadNode);
testNodes.addNodeRef(downloadNode);
DOWNLOAD_SERVICE.cancelDownload(downloadNode);
DownloadStatus status = getDownloadStatus(downloadNode);
int retryCount = 0;
while (status.getStatus() != Status.CANCELLED && retryCount < 5)
{
retryCount++;
Thread.sleep(PAUSE_TIME);
status = getDownloadStatus(downloadNode);
}
Assert.assertEquals(Status.CANCELLED, status.getStatus());
}
/**
* This test verifies that a user is given the correct file, when it is checked. The user who checked out
* the file should get the working copy, while any other user should get the default version.
* @throws InterruptedException
*/
@Test public void workingCopies() throws InterruptedException
{
final Set<String> preCheckoutExpectedEntries = new TreeSet<String>();
preCheckoutExpectedEntries.add("level1Folder2/");
preCheckoutExpectedEntries.add("level1Folder2/level2File.txt");
preCheckoutExpectedEntries.add("level1Folder2/fileToCheckout.txt");
validateWorkingCopyFolder(preCheckoutExpectedEntries, level1Folder2, TEST_USER.getUsername());
validateWorkingCopyFolder(preCheckoutExpectedEntries, level1Folder2, TEST_USER2.getUsername());
Authentication previousAuth = AuthenticationUtil.getFullAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER.getUsername());
NodeRef workingCopy;
try
{
workingCopy = CHECK_OUT_CHECK_IN_SERVICE.checkout(fileToCheckout);
}
finally
{
AuthenticationUtil.setFullAuthentication(previousAuth);
}
try
{
validateWorkingCopyFolder(preCheckoutExpectedEntries, level1Folder2, TEST_USER2.getUsername());
final Set<String> postCheckoutExpectedEntries = new TreeSet<String>();
postCheckoutExpectedEntries.add("level1Folder2/");
postCheckoutExpectedEntries.add("level1Folder2/level2File.txt");
postCheckoutExpectedEntries.add("level1Folder2/fileToCheckout (Working Copy).txt");
validateWorkingCopyFolder(postCheckoutExpectedEntries, level1Folder2, TEST_USER.getUsername());
}
finally
{
previousAuth = AuthenticationUtil.getFullAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER.getUsername());
try
{
CHECK_OUT_CHECK_IN_SERVICE.checkin(workingCopy, null);
}
finally
{
AuthenticationUtil.setFullAuthentication(previousAuth);
}
}
validateWorkingCopyFolder(preCheckoutExpectedEntries, level1Folder2, TEST_USER.getUsername());
validateWorkingCopyFolder(preCheckoutExpectedEntries, level1Folder2, TEST_USER2.getUsername());
}
private void validateWorkingCopyFolder(final Set<String> expectedEntries, final NodeRef folder, final String userID) throws InterruptedException
{
Authentication previousAuthentication = AuthenticationUtil.getFullAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(userID);
try
{
final NodeRef downloadNode = DOWNLOAD_SERVICE.createDownload(new NodeRef[] {folder}, true);
waitForDownload(downloadNode);
validateEntries(getEntries(downloadNode), expectedEntries, true);
}
finally
{
AuthenticationUtil.setFullAuthentication(previousAuthentication);
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import org.alfresco.service.cmr.download.DownloadStatus;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Service for updating the status of a download.
*
* @author Alex Miller
*/
public interface DownloadStatusUpdateService
{
/**
* Update and persist the status of the download.
*
* Implementations should only do this if sequenceNumber is greater than
* the sequenceNumber of the previous update, to prevent out of order
* updates.
*
* @param nodeRef The download node, whose status is to be updated.
* @param status The new status
* @param sequenceNumber
*/
void update(NodeRef nodeRef, DownloadStatus status, int sequenceNumber);
}

View File

@@ -0,0 +1,56 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import org.alfresco.service.cmr.download.DownloadStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.mockito.internal.progress.SequenceNumber;
/**
* Implementation class responsible for update the status of a download node.
*
* @author Alex Miller
*/
public class DownloadStatusUpdateServiceImpl implements DownloadStatusUpdateService
{
// Dependencies
private DownloadStorage storage;
// Dependency setters
public void setStorage(DownloadStorage storage)
{
this.storage = storage;
}
@Override
public void update(NodeRef nodeRef, DownloadStatus status, int sequenceNumber)
{
// Update the status of the download node, if and only if sequenceNumber is
// greater than the sequence number of the last update.
int currentSequenceNumber = storage.getSequenceNumber(nodeRef);
if (currentSequenceNumber < sequenceNumber)
{
storage.updateStatus(nodeRef, status);
}
}
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2005-2012 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.repo.download;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.query.CannedQueryFactory;
import org.alfresco.query.CannedQueryResults;
import org.alfresco.repo.download.cannedquery.DownloadEntity;
import org.alfresco.repo.download.cannedquery.GetDownloadsCannedQuery;
import org.alfresco.repo.download.cannedquery.GetDownloadsCannedQueryFactory;
import org.alfresco.repo.importer.ImporterBootstrap;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.node.SystemNodeUtils;
import org.alfresco.service.cmr.download.DownloadRequest;
import org.alfresco.service.cmr.download.DownloadStatus;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.registry.NamedObjectRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This class is responsible for the persistence of {@link DownloadDefinition} objects using lower-level
* repo services such as the {@link NodeService}. The higher-level business logic around these CRUD calls
* is contained within the {@link DownloadServiceImpl}.
*
* @author Alex Miller
*/
public class DownloadStorage
{
private static final Log log = LogFactory.getLog(DownloadStorage.class);
// service dependencies
private ImporterBootstrap bootstrap;
private Repository repositoryHelper;
private NodeService nodeService;
private NamespaceService namespaceService;
private NamedObjectRegistry<CannedQueryFactory<? extends Object>> queryRegistry;
public void setImporterBootstrap(ImporterBootstrap bootstrap)
{
this.bootstrap = bootstrap;
}
public void setQueryRegistry(NamedObjectRegistry<CannedQueryFactory<? extends Object>> queryRegistry)
{
this.queryRegistry = queryRegistry;
}
public void setRepositoryHelper(Repository repositoryHelper)
{
this.repositoryHelper = repositoryHelper;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* This method finds the SyncSet Definition Container NodeRef, creating one if it does not exist.
*
* @return the syncset definition container
*/
public NodeRef getOrCreateDowloadContainer()
{
NodeRef downloadsContainer = getContainer();
if (downloadsContainer == null)
{
if (log.isInfoEnabled())
log.info("Lazy creating the Downloads System Container ");
downloadsContainer = SystemNodeUtils.getOrCreateSystemChildContainer(getContainerQName(), nodeService, repositoryHelper).getFirst();
}
return downloadsContainer;
}
private NodeRef getContainer()
{
return SystemNodeUtils.getSystemChildContainer(getContainerQName(), nodeService, repositoryHelper);
}
private QName getContainerQName()
{
String name = bootstrap.getConfiguration().getProperty("system.downloads_container.childname");
QName container = QName.createQName(name, namespaceService);
return container;
}
public NodeRef createDownloadNode(boolean recursive)
{
NodeRef downloadsContainer = getOrCreateDowloadContainer();
Map<QName, Serializable> downloadProperties = new HashMap<QName, Serializable>();
downloadProperties.put(DownloadModel.PROP_RECURSIVE, recursive);
ChildAssociationRef newChildAssoc = nodeService.createNode(downloadsContainer,
ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN,
DownloadModel.TYPE_DOWNLOAD,
downloadProperties);
final NodeRef downloadNodeRef = newChildAssoc.getChildRef();
if (log.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Created Download. ")
.append("', Download-NodeRef=");
log.debug(msg.toString());
}
return downloadNodeRef;
}
public void cancelDownload(NodeRef downloadNodeRef)
{
validateNode(downloadNodeRef);
nodeService.setProperty(downloadNodeRef, DownloadModel.PROP_CANCELLED, true);
}
public boolean isCancelled(NodeRef downloadNodeRef)
{
validateNode(downloadNodeRef);
return (Boolean)nodeService.getProperty(downloadNodeRef, DownloadModel.PROP_CANCELLED);
}
public void addNodeToDownload(NodeRef downloadNode, NodeRef nodeToAdd)
{
nodeService.createAssociation(downloadNode, nodeToAdd, DownloadModel.ASSOC_REQUESTED_NODES);
if (log.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Node added to Download-NodeRef '")
.append(downloadNode).append("'. RequestedNode=")
.append(nodeToAdd);
log.debug(msg.toString());
}
}
public DownloadRequest getDownloadRequest(NodeRef downloadNodeRef)
{
validateNode(downloadNodeRef);
Map<QName, Serializable> properties = nodeService.getProperties(downloadNodeRef);
List<AssociationRef> requestedNodes = nodeService.getTargetAssocs(downloadNodeRef, DownloadModel.ASSOC_REQUESTED_NODES);
return new DownloadRequest((Boolean)properties.get(DownloadModel.PROP_RECURSIVE), requestedNodes, (String)properties.get(ContentModel.PROP_CREATOR));
}
private void validateNode(NodeRef downloadNodeRef)
{
if (nodeService.getType(downloadNodeRef).equals(DownloadModel.TYPE_DOWNLOAD) == false)
{
throw new IllegalArgumentException("Invlaid node type for nodeRef:-" + downloadNodeRef);
}
}
public DownloadStatus getDownloadStatus(NodeRef downloadNodeRef)
{
validateNode(downloadNodeRef);
Map<QName, Serializable> properties = nodeService.getProperties(downloadNodeRef);
Long done = (Long)properties.get(DownloadModel.PROP_DONE);
Long total = (Long)properties.get(DownloadModel.PROP_TOTAL);
Long filesAdded = (Long)properties.get(DownloadModel.PROP_FILES_ADDED);
Long totalFiles = (Long)properties.get(DownloadModel.PROP_TOTAL_FILES);
return new DownloadStatus(DownloadStatus.Status.valueOf((String)properties.get(DownloadModel.PROP_STATUS)),
done != null ? done.longValue() : 0l,
total != null ? total.longValue() : 0l,
filesAdded != null ? filesAdded.longValue() : 0l,
totalFiles != null ? totalFiles.longValue() : 0l);
}
public int getSequenceNumber(NodeRef nodeRef)
{
validateNode(nodeRef);
Serializable sequenceNumber = nodeService.getProperty(nodeRef, DownloadModel.PROP_SEQUENCE_NUMBER);
return ((Integer)sequenceNumber).intValue();
}
public void updateStatus(NodeRef nodeRef, DownloadStatus status)
{
validateNode(nodeRef);
nodeService.setProperty(nodeRef, DownloadModel.PROP_STATUS, status.getStatus().toString());
nodeService.setProperty(nodeRef, DownloadModel.PROP_DONE, new Long(status.getDone()));
nodeService.setProperty(nodeRef, DownloadModel.PROP_TOTAL, new Long(status.getTotal()));
nodeService.setProperty(nodeRef, DownloadModel.PROP_FILES_ADDED, status.getFilesAdded());
nodeService.setProperty(nodeRef, DownloadModel.PROP_TOTAL_FILES, status.getTotalFiles());
}
/**
* Get all the downloads created before before.
*/
public List<List<DownloadEntity>> getDownloadsCreatedBefore(Date before)
{
NodeRef container = getContainer();
if (container == null)
{
return Collections.emptyList();
}
// Grab the factory
GetDownloadsCannedQueryFactory getDownloadCannedQueryFactory =
(GetDownloadsCannedQueryFactory)queryRegistry.getNamedObject("downloadGetDownloadsCannedQueryFactory");
// Run the canned query
GetDownloadsCannedQuery cq = (GetDownloadsCannedQuery)getDownloadCannedQueryFactory.getDownloadsCannedQuery(container, before);
// Execute the canned query
CannedQueryResults<DownloadEntity> results = cq.execute();
return results.getPages();
}
/**
* Delete the download node identified by nodeRef
* @param nodeRef
*/
public void delete(NodeRef nodeRef)
{
validateNode(nodeRef);
nodeService.deleteNode(nodeRef);
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import java.util.List;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.tenant.Tenant;
import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.service.cmr.download.DownloadService;
import org.joda.time.DateTime;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* Executes the clean up of download nodes.
*
* @author Alex Miller
*/
public class DownloadsCleanupJob implements Job
{
private static final String KEY_DOWNLOAD_SERVICE = "downloadService";
private static final String KEY_TENANT_ADMIN_SERVICE = "tenantAdminService";
private static final String KEY_MAX_AGE = "maxAgeInMinutes";
/*
* @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
JobDataMap jobData = context.getJobDetail().getJobDataMap();
// extract the services and max age to use
final DownloadService downloadService = (DownloadService)jobData.get(KEY_DOWNLOAD_SERVICE);
final TenantAdminService tenantAdminService = (TenantAdminService)jobData.get(KEY_TENANT_ADMIN_SERVICE);
final int maxAgeInMinutes = Integer.parseInt((String)jobData.get(KEY_MAX_AGE));
final DateTime before = new DateTime().minusMinutes(maxAgeInMinutes);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
downloadService.deleteDownloads(before.toDate());
return null;
}
}, AuthenticationUtil.getSystemUserName());
if ((tenantAdminService != null) && tenantAdminService.isEnabled())
{
List<Tenant> tenants = tenantAdminService.getAllTenants();
for (Tenant tenant : tenants)
{
TenantUtil.runAsSystemTenant(new TenantRunAsWork<Object>()
{
public Object doWork() throws Exception
{
downloadService.deleteDownloads(before.toDate());
return null;
}
}, tenant.getTenantDomain());
}
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.ParameterCheck;
import org.springframework.beans.factory.InitializingBean;
/**
* Implementation of {@link ActionServiceHelper} which schedules the zip creation process to run in the same alfresco node
* as the caller.
*
* @author Alex Miller
*/
public class LocalActionServiceHelper implements InitializingBean, ActionServiceHelper
{
private ActionService localActionService;
public void setLocalActionService(ActionService localActionService)
{
this.localActionService = localActionService;
}
@Override
public void executeAction(NodeRef downloadNode)
{
Action action = localActionService.createAction("createDownloadArchiveAction");
action.setExecuteAsynchronously(true);
localActionService.executeAction(action, downloadNode);
}
@Override
public void afterPropertiesSet() throws Exception
{
ParameterCheck.mandatory("localActionServer", localActionService);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.springframework.util.FileCopyUtils;
/**
* {@link ContentServiceHelper} implementation which uses the local ContentService to update the content.
*
* @author Alex Miller
*/
public class LocalContentServiceHelper implements ContentServiceHelper
{
private ContentService contentService;
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
@Override
public void updateContent(final NodeRef downloadNode, final File archiveFile) throws ContentIOException, FileNotFoundException, IOException
{
//RunAsSystem to mimic clustered behavior, and bypass quotas when using S3 storage.
AuthenticationUtil.runAsSystem(new RunAsWork<Object>()
{
@Override
public Object doWork() throws Exception
{
ContentWriter writer = contentService.getWriter(downloadNode, ContentModel.PROP_CONTENT, true);
FileCopyUtils.copy(new FileInputStream(archiveFile), writer.getContentOutputStream());
return null;
}
});
}
}

View File

@@ -0,0 +1,300 @@
/*
* 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.download;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.view.ExporterContext;
import org.alfresco.service.cmr.view.ExporterException;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.UnicodeExtraFieldPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for exporting node content to a ZIP file
*
* @author Alex Miller
*/
public class ZipDownloadExporter extends BaseExporter
{
private static Logger log = LoggerFactory.getLogger(ZipDownloadExporter.class);
private static final String PATH_SEPARATOR = "/";
protected ZipArchiveOutputStream zipStream;
private NodeRef downloadNodeRef;
private int sequenceNumber = 1;
private long total;
private long done;
private long totalFileCount;
private long filesAddedCount;
private RetryingTransactionHelper transactionHelper;
private DownloadStorage downloadStorage;
private DownloadStatusUpdateService updateService;
private Deque<Pair<String, NodeRef>> path = new LinkedList<Pair<String, NodeRef>>();
private String currentName;
private OutputStream outputStream;
/**
* Construct
*
* @param destDir
* @param zipFile
* @param transactionHelper
* @param l
* @param actionedUponNodeRef
* @param dataFile
* @param contentDir
*/
public ZipDownloadExporter(File zipFile, CheckOutCheckInService checkOutCheckInService, NodeService nodeService, RetryingTransactionHelper transactionHelper, DownloadStatusUpdateService updateService, DownloadStorage downloadStorage, NodeRef downloadNodeRef, long total, long totalFileCount)
{
super(checkOutCheckInService, nodeService);
try
{
this.outputStream = new FileOutputStream(zipFile);
this.updateService = updateService;
this.transactionHelper = transactionHelper;
this.downloadStorage = downloadStorage;
this.downloadNodeRef = downloadNodeRef;
this.total = total;
this.totalFileCount = totalFileCount;
}
catch (FileNotFoundException e)
{
throw new ExporterException("Failed to create zip file", e);
}
}
@Override
public void start(final ExporterContext context)
{
zipStream = new ZipArchiveOutputStream(outputStream);
// NOTE: This encoding allows us to workaround bug...
// http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807
zipStream.setEncoding("UTF-8");
zipStream.setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy.ALWAYS);
zipStream.setUseLanguageEncodingFlag(true);
zipStream.setFallbackToUTF8(true);
}
@Override
public void startNode(NodeRef nodeRef)
{
this.currentName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
path.push(new Pair<String, NodeRef>(currentName, nodeRef));
if (ContentModel.TYPE_FOLDER.equals(nodeService.getType(nodeRef)))
{
String path = getPath() + PATH_SEPARATOR;
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(path);
try
{
zipStream.putArchiveEntry(archiveEntry);
zipStream.closeArchiveEntry();
}
catch (IOException e)
{
throw new ExporterException("Unexpected IOException adding folder entry", e);
}
}
}
@Override
public void contentImpl(NodeRef nodeRef, QName property, InputStream content, ContentData contentData, int index)
{
// if the content stream to output is empty, then just return content descriptor as is
if (content == null)
{
return;
}
try
{
// ALF-2016
ZipArchiveEntry zipEntry=new ZipArchiveEntry(getPath());
zipStream.putArchiveEntry(zipEntry);
// copy export stream to zip
copyStream(zipStream, content);
zipStream.closeArchiveEntry();
filesAddedCount = filesAddedCount + 1;
}
catch (IOException e)
{
throw new ExporterException("Failed to zip export stream", e);
}
}
@Override
public void endNode(NodeRef nodeRef)
{
path.pop();
}
@Override
public void end()
{
try
{
zipStream.close();
}
catch (IOException error)
{
throw new ExporterException("Unexpected error closing zip stream!", error);
}
}
private String getPath()
{
if (path.size() < 1)
{
throw new IllegalStateException("No elements in path!");
}
Iterator<Pair<String, NodeRef>> iter = path.descendingIterator();
StringBuilder pathBuilder = new StringBuilder();
while (iter.hasNext())
{
Pair<String, NodeRef> element = iter.next();
pathBuilder.append(element.getFirst());
if (iter.hasNext())
{
pathBuilder.append(PATH_SEPARATOR);
}
}
return pathBuilder.toString();
}
/**
* Copy input stream to output stream
*
* @param output output stream
* @param in input stream
* @throws IOException
*/
private void copyStream(OutputStream output, InputStream in)
throws IOException
{
byte[] buffer = new byte[2048 * 10];
int read = in.read(buffer, 0, 2048 *10);
while (read != -1)
{
output.write(buffer, 0, read);
done = done + read;
updateStatus();
checkCancelled();
read = in.read(buffer, 0, 2048 *10);
}
}
private void checkCancelled()
{
boolean downloadCancelled = transactionHelper.doInTransaction(new RetryingTransactionCallback<Boolean>()
{
@Override
public Boolean execute() throws Throwable
{
return downloadStorage.isCancelled(downloadNodeRef);
}
}, true, true);
if ( downloadCancelled == true)
{
log.debug("Download cancelled");
throw new DownloadCancelledException();
}
}
private void updateStatus()
{
transactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
{
@Override
public Object execute() throws Throwable
{
DownloadStatus status = new DownloadStatus(Status.IN_PROGRESS, done, total, filesAddedCount, totalFileCount);
updateService.update(downloadNodeRef, status, getNextSequenceNumber());
return null;
}
}, false, true);
}
public int getNextSequenceNumber()
{
return sequenceNumber++;
}
public long getDone()
{
return done;
}
public long getTotal()
{
return total;
}
public long getFilesAdded()
{
return filesAddedCount;
}
public long getTotalFiles()
{
return totalFileCount;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download.cannedquery;
import org.alfresco.repo.query.NodeBackedEntity;
/**
* Download Entity - used by GetDownloads CQ
*
* @author Alex Miller
*/
public class DownloadEntity extends NodeBackedEntity
{
/**
* Default constructor
*/
public DownloadEntity()
{
super();
}
public DownloadEntity(Long parentNodeId, Long nameQNameId, Long contentTypeQNameId)
{
super(parentNodeId, nameQNameId, contentTypeQNameId);
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download.cannedquery;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.alfresco.query.CannedQueryParameters;
import org.alfresco.repo.domain.query.CannedQueryDAO;
import org.alfresco.repo.security.permissions.impl.acegi.AbstractCannedQueryPermissions;
import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityBean;
import org.alfresco.service.cmr.download.DownloadService;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
/**
* This class provides the GetDownloads canned queries} used by the
* {@link DownloadService}.deleteDOwnloads.
*
* @author Alex Miller
*/
public class GetDownloadsCannedQuery extends AbstractCannedQueryPermissions<DownloadEntity>
{
private static final String QUERY_NAMESPACE = "alfresco.query.downloads";
private static final String QUERY_SELECT_GET_DOWNLOADS = "select_GetDownloadsBeforeQuery";
private final CannedQueryDAO cannedQueryDAO;
public GetDownloadsCannedQuery(
CannedQueryDAO cannedQueryDAO,
MethodSecurityBean<DownloadEntity> methodSecurity,
CannedQueryParameters params)
{
super(params, methodSecurity);
this.cannedQueryDAO = cannedQueryDAO;
}
@Override
protected List<DownloadEntity> queryAndFilter(CannedQueryParameters parameters)
{
Object paramBeanObj = parameters.getParameterBean();
if (paramBeanObj == null)
{
throw new NullPointerException("Null GetDownloadss query params");
}
GetDownloadsCannedQueryParams paramsBean = (GetDownloadsCannedQueryParams)paramBeanObj;
// note: refer to SQL for specific DB filtering (eg.parent node and optionally blog integration aspect, etc)
List<DownloadEntity> results = cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_DOWNLOADS, paramBeanObj, 0, Integer.MAX_VALUE);
List<DownloadEntity> filteredResults = new ArrayList<DownloadEntity>();
for (DownloadEntity entity : results)
{
Date createdDate = DefaultTypeConverter.INSTANCE.convert(Date.class, entity.getCreatedDate());
Date modifiedDate = DefaultTypeConverter.INSTANCE.convert(Date.class, entity.getModifiedDate());
if (modifiedDate == null)
{
modifiedDate = createdDate;
}
if (modifiedDate.before(paramsBean.getBefore()))
{
filteredResults.add(entity);
}
else
{
break;
}
}
return filteredResults;
}
@Override
protected boolean isApplyPostQuerySorting()
{
// No post-query sorting. It's done within the queryAndFilter() method above.
return false;
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download.cannedquery;
import java.util.Date;
import org.alfresco.model.ContentModel;
import org.alfresco.query.CannedQuery;
import org.alfresco.query.CannedQueryFactory;
import org.alfresco.query.CannedQueryParameters;
import org.alfresco.repo.download.DownloadModel;
import org.alfresco.repo.query.AbstractQNameAwareCannedQueryFactory;
import org.alfresco.service.cmr.download.DownloadService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.ParameterCheck;
/**
* A {@link CannedQueryFactory} for queries relating to {@link DownloadEntity download entities}.
*
* @author Alex Miller
*
* @see DownloadService#deleteDownloads(Date)
*/
public class GetDownloadsCannedQueryFactory extends AbstractQNameAwareCannedQueryFactory<DownloadEntity>
{
@Override
public void afterPropertiesSet() throws Exception
{
super.afterPropertiesSet();
}
public CannedQuery<DownloadEntity> getDownloadsCannedQuery(NodeRef containerNode, Date before)
{
ParameterCheck.mandatory("before", before);
GetDownloadsCannedQueryParams parameterBean = new GetDownloadsCannedQueryParams
(
getNodeId(containerNode),
getQNameId(ContentModel.PROP_NAME),
getQNameId(DownloadModel.TYPE_DOWNLOAD),
before
);
CannedQueryParameters params = new CannedQueryParameters(parameterBean);
final GetDownloadsCannedQuery cq = new GetDownloadsCannedQuery(
cannedQueryDAO, methodSecurity, params
);
return cq;
}
/*
* @see org.alfresco.query.CannedQueryFactory#getCannedQuery(org.alfresco.query.CannedQueryParameters)
*/
@Override
public CannedQuery<DownloadEntity> getCannedQuery(CannedQueryParameters parameters)
{
return new GetDownloadsCannedQuery(cannedQueryDAO, methodSecurity, parameters);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.download.cannedquery;
import java.util.Date;
/**
* Query parameters for GetDownloadsCannedQuery
*
* @author Alex Miller
*/
public class GetDownloadsCannedQueryParams extends DownloadEntity
{
private Date before;
public GetDownloadsCannedQueryParams(Long parentNodeId, Long nameQNameId, Long contentTypeQNameId, Date before)
{
super(parentNodeId, nameQNameId, contentTypeQNameId);
this.before = before;
}
public Date getBefore()
{
return before;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2005-2012 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.service.cmr.download;
import java.util.ArrayList;
import java.util.List;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* DownloadRequest data transfer object.
*
* @author Alex Miller
*/
public class DownloadRequest
{
private String owner;
private boolean recursive;
private List<AssociationRef> requestedNodes;
public DownloadRequest(boolean recursive, List<AssociationRef> requestedNodes, String owner)
{
this.owner = owner;
this.recursive = recursive;
this.requestedNodes = requestedNodes;
}
public List<AssociationRef> getRequetedNodes()
{
return requestedNodes;
}
public NodeRef[] getRequetedNodeRefs()
{
List<NodeRef> requestedNodeRefs = new ArrayList<NodeRef>(requestedNodes.size());
for (AssociationRef requestedNode : requestedNodes)
{
requestedNodeRefs.add(requestedNode.getTargetRef());
}
return requestedNodeRefs.toArray(new NodeRef[requestedNodeRefs.size()]);
}
/**
* @return
*/
public String getOwner()
{
return owner;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.service.cmr.download;
import java.util.Date;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Zip download service.
*
* Implementations are responsible for triggering the Zip creation process and
* reporting on the status of the of this process.
*
* @author Alex Miller
*/
public interface DownloadService
{
/**
* Start the creation of a downlaodable archive file containing the content
* from the given nodeRefs.
*
* Implementations are expected to do this asynchronously, with clients
* using the returned NodeRef to check on progress.
* Initially, only zip files will be supported, however this could be
* extended in the future, to support additional archive types.
*
* @param nodeRefs NodeRefs of content to be added to the archive file
* @param recusirsive Recurse into container nodes
* @return Reference to node which will eventually contain the archive file
*/
public NodeRef createDownload(NodeRef[] nodeRefs, boolean recusirsive);
/**
* Get the status of the of the download identified by downloadNode.
*/
public DownloadStatus getDownloadStatus(NodeRef downloadNode);
/**
* Delete downloads created before before.
*
* @param before
*/
public void deleteDownloads(Date before);
/**
* Cancel a download request
*
* @param downloadNodeRef NodeRef of the download to cancel
*/
public void cancelDownload(NodeRef downloadNodeRef);
}

View File

@@ -0,0 +1,126 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.service.cmr.download;
import java.io.Serializable;
/**
* Immutable data transfer object representing the status of a download.
*
* Provides the current status and an indication of the progress. Prgress is
* measured by comparing done against total. Total gives an indication of the
* total work, while done indicates how much has been completed.
*
* @author amiller
*/
public class DownloadStatus implements Serializable
{
private static final long serialVersionUID = 4513872550314507598L;
public enum Status {
PENDING,
IN_PROGRESS,
DONE,
MAX_CONTENT_SIZE_EXCEEDED,
CANCELLED
}
private long done;
private long total;
private long filesAddedCount;
private long totalFileCount;
private Status status;
/**
* Default constructor
* @param status Current status of the download
* @param done Done count
* @param total Total to be de done
* @param filesAddedCount Number of files added to the archive
* @param totalFiles The number of files that will eventually be added to the archive
*/
public DownloadStatus(Status status, long done, long total, long filesAdded, long totalFiles)
{
this.status = status;
this.done = done;
this.total = total;
this.filesAddedCount = filesAdded;
this.totalFileCount = totalFiles;
}
/**
* @return The percentage complete, calculated by multiplying done by 100 and dividing by total.
*/
public long getPercentageComplete()
{
return (done * 100) / total;
}
/**
* @return true if status is DONE, false otherwise.
*/
public boolean isComplete()
{
return status == Status.DONE;
}
/**
* @return the current status
*/
public Status getStatus()
{
return status;
}
/**
* @return the current done count
*/
public long getDone()
{
return done;
}
/**
* @return the total, to be done.
*/
public long getTotal()
{
return total;
}
/**
* @return the total number of files in the download archive
*/
public long getTotalFiles()
{
return totalFileCount;
}
/**
* @return the number of files added to the download archive
* @return
*/
public long getFilesAdded()
{
return filesAddedCount;
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.service.cmr.download;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Service for updating the status of a download.
*
* @author Alex Miller
*/
public interface DownloadStatusUpdateService
{
void update(NodeRef nodeRef, DownloadStatus status, int sequenceNumber);
}

View File

@@ -0,0 +1,30 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* Defines the contracts for creating archive files containing specified
* content from the repository.
*
* The DownlaodService is a client (Share) facing service responsible for
* creating a node containing enough information for the download archive to
* be created, and reporting on the progress of the creation process.
*
* @author Alex Miller
*/
package org.alfresco.service.cmr.download;