mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged V3.4-BUG-FIX to HEAD
29716: ALF-4029: FileContentStore does not remove empty folders when deleting URLs delete() now crawls back up the dir hierarchy deleting directories until a non-empty parent is found. 29767: ALF-9351 No exception with invalid permission definitions - DTD/Schema validation added - Corrected permissionDefinitions.xml (contained extra -->) so failed validation 29797: ALF-9916 Audit user actions for site (added st:site to list of types in filter) 29800: ALF-5499 IndexOutOfBoundsException in QuickSort Need to synchronize access to JSF session beans. Added a filter that will use the HttpSession (if it exists) as the monitor for a synchronized block so that only one request per session is processed at any time. Approach taken in preference to adding synchronized blocks/methods or locks to over 200 session beans in the Alfresco Explorer UI. 29801: ALF-9190: If a user is invited to a site but joins the site independently, they end up with the "Consumer" role, regardless of the role they were invited with 29805: ALF-4029: added utility to make some rough timings. 29819: Merged DEV/TEMPORARY to V3.4-BUG-FIX (reviewed by Erik) 29815: ALF-8414 : Remove button does not show on Flash upload in Share Changed flash-upload.css styles to correct upload dialog display in IE6/IE7. Changes were tested in all supported browsers. 29826: Fix for ALF-9930 29836: Merged V3.4-TEAM to V3.4-BUG-FIX 27772: Incorrect behavior of enabling Google docs (Really: Forms get submitted twice in certain circumstances) 29839: ALF-9351 No exception with invalid permission definitions - Added permissionsDefinitions.xml to RM's permissionsModelDAO spring bean def (root cause of build errors) - Use UTF-8 encoding rather than server default when writing out modified form of model xml - Use byte[] rather than a temp file (model files should be small) - Added and then commented out an approach that did not require the need to write out the model xml (works for schema but not dtd. See notes in javax.xml.validation.SchemaFactory) - Fixed problem with RM recordsPermissionModel.xml (select -> selected) 29841: Fixed ALF-9826 "Folder is copied without content even if 'Apply rule to subfolders' check-box is checked" - Added "deep-copy" option for Copy action git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29863 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -406,49 +406,49 @@
|
|||||||
<!-- Permissions specific to avm website folder -->
|
<!-- Permissions specific to avm website folder -->
|
||||||
<!-- ========================================== -->
|
<!-- ========================================== -->
|
||||||
|
|
||||||
<permissionSet type="wcm:avmfolder" expose="selected"> -->
|
<permissionSet type="wcm:avmfolder" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
||||||
</permissionSet>
|
</permissionSet>
|
||||||
|
|
||||||
<permissionSet type="wcm:avmplainfolder" expose="selected"> -->
|
<permissionSet type="wcm:avmplainfolder" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
||||||
</permissionSet>
|
</permissionSet>
|
||||||
|
|
||||||
<permissionSet type="wcm:avmlayeredfolder" expose="selected"> -->
|
<permissionSet type="wcm:avmlayeredfolder" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
||||||
</permissionSet>
|
</permissionSet>
|
||||||
|
|
||||||
<permissionSet type="wcm:avmcontent" expose="selected"> -->
|
<permissionSet type="wcm:avmcontent" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
||||||
</permissionSet>
|
</permissionSet>
|
||||||
|
|
||||||
<permissionSet type="wcm:avmplaincontent" expose="selected"> -->
|
<permissionSet type="wcm:avmplaincontent" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
||||||
</permissionSet>
|
</permissionSet>
|
||||||
|
|
||||||
<permissionSet type="wcm:avmlayeredcontent" expose="selected"> -->
|
<permissionSet type="wcm:avmlayeredcontent" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
<permissionGroup name="ContentReviewer" extends="true" expose="true" />
|
||||||
</permissionSet>
|
</permissionSet>
|
||||||
|
|
||||||
<permissionSet type="wca:webfolder" expose="selected"> -->
|
<permissionSet type="wca:webfolder" expose="selected">
|
||||||
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
<permissionGroup name="ContentManager" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
<permissionGroup name="ContentPublisher" extends="true" expose="true" />
|
||||||
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
<permissionGroup name="ContentContributor" extends="true" expose="true" />
|
||||||
|
@@ -22,6 +22,9 @@
|
|||||||
<property name="model">
|
<property name="model">
|
||||||
<value>alfresco/model/permissionDefinitions.xml</value>
|
<value>alfresco/model/permissionDefinitions.xml</value>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="dtdSchema">
|
||||||
|
<value>alfresco/model/permissionSchema.dtd</value>
|
||||||
|
</property>
|
||||||
<property name="nodeService">
|
<property name="nodeService">
|
||||||
<ref bean="nodeService" />
|
<ref bean="nodeService" />
|
||||||
</property>
|
</property>
|
||||||
|
@@ -350,7 +350,7 @@ audit.config.strict=false
|
|||||||
# Audit map filter for AccessAuditor - restricts recorded events to user driven events
|
# Audit map filter for AccessAuditor - restricts recorded events to user driven events
|
||||||
audit.filter.alfresco-access.default.enabled=true
|
audit.filter.alfresco-access.default.enabled=true
|
||||||
audit.filter.alfresco-access.transaction.user=~System;~null;.*
|
audit.filter.alfresco-access.transaction.user=~System;~null;.*
|
||||||
audit.filter.alfresco-access.transaction.type=cm:folder;cm:content
|
audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site
|
||||||
audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.*
|
audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.*
|
||||||
|
|
||||||
|
|
||||||
|
@@ -79,13 +79,13 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
*
|
*
|
||||||
* The following properties are set by default to discard events where the user is
|
* The following properties are set by default to discard events where the user is
|
||||||
* 'null' or 'System', the node path is '/sys:archivedItem' or under '/ver:' or
|
* 'null' or 'System', the node path is '/sys:archivedItem' or under '/ver:' or
|
||||||
* the node type is not 'cm:folder' or 'cm:content'. These values result in events
|
* the node type is not 'cm:folder', 'cm:content' or 'st:site'. These values result
|
||||||
* only being recorded if they are initiated by users of the system. These vales may
|
* in events only being recorded for common actions initiated by users of the system.
|
||||||
* be overridden if required.
|
* These vales may be overridden if required.
|
||||||
* <pre>
|
* <pre>
|
||||||
* audit.filter.alfresco-access.default.enabled=true
|
* audit.filter.alfresco-access.default.enabled=true
|
||||||
* audit.filter.alfresco-access.transaction.user=~System;~null;.*
|
* audit.filter.alfresco-access.transaction.user=~System;~null;.*
|
||||||
* audit.filter.alfresco-access.transaction.type=cm:folder;cm:content
|
* audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site
|
||||||
* audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.*
|
* audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.*
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
|
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.filestore;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.content.ContentStore;
|
||||||
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
|
import org.alfresco.repo.content.cleanup.ContentStoreCleaner;
|
||||||
|
import org.alfresco.repo.content.cleanup.ContentStoreCleanerListener;
|
||||||
|
import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner;
|
||||||
|
import org.alfresco.repo.domain.avm.AVMNodeDAO;
|
||||||
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
|
import org.alfresco.repo.lock.JobLockService;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
|
import org.alfresco.service.ServiceRegistry;
|
||||||
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentData;
|
||||||
|
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.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
|
import org.alfresco.util.GUID;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
|
public class DeletionMetricsRunner
|
||||||
|
{
|
||||||
|
private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||||
|
private ContentService contentService;
|
||||||
|
private NodeService nodeService;
|
||||||
|
private TransactionService transactionService;
|
||||||
|
private JobLockService jobLockService;
|
||||||
|
private ContentStoreCleaner cleaner;
|
||||||
|
private EagerContentStoreCleaner eagerCleaner;
|
||||||
|
private FileContentStore store;
|
||||||
|
private ContentStoreCleanerListener listener;
|
||||||
|
private int deletedUrls;
|
||||||
|
|
||||||
|
private final int numOrphans = 1000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public DeletionMetricsRunner()
|
||||||
|
{
|
||||||
|
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry");
|
||||||
|
contentService = serviceRegistry.getContentService();
|
||||||
|
nodeService = serviceRegistry.getNodeService();
|
||||||
|
transactionService = serviceRegistry.getTransactionService();
|
||||||
|
jobLockService = serviceRegistry.getJobLockService();
|
||||||
|
TransactionService transactionService = serviceRegistry.getTransactionService();
|
||||||
|
DictionaryService dictionaryService = serviceRegistry.getDictionaryService();
|
||||||
|
AVMNodeDAO avmNodeDAO = (AVMNodeDAO) ctx.getBean("newAvmNodeDAO");
|
||||||
|
ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
|
||||||
|
|
||||||
|
// we need a store
|
||||||
|
store = (FileContentStore) ctx.getBean("fileContentStore");
|
||||||
|
|
||||||
|
|
||||||
|
// and a listener
|
||||||
|
List<ContentStoreCleanerListener> listeners = new ArrayList<ContentStoreCleanerListener>(2);
|
||||||
|
listener = new CleanerListener();
|
||||||
|
listeners.add(listener);
|
||||||
|
|
||||||
|
// Construct the test cleaners
|
||||||
|
eagerCleaner = (EagerContentStoreCleaner) ctx.getBean("eagerContentStoreCleaner");
|
||||||
|
eagerCleaner.setEagerOrphanCleanup(false);
|
||||||
|
eagerCleaner.setStores(Collections.singletonList((ContentStore) store));
|
||||||
|
eagerCleaner.setListeners(listeners);
|
||||||
|
|
||||||
|
cleaner = new ContentStoreCleaner();
|
||||||
|
cleaner.setEagerContentStoreCleaner(eagerCleaner);
|
||||||
|
cleaner.setJobLockService(jobLockService);
|
||||||
|
cleaner.setContentDataDAO(contentDataDAO);
|
||||||
|
cleaner.setTransactionService(transactionService);
|
||||||
|
cleaner.setDictionaryService(dictionaryService);
|
||||||
|
cleaner.setContentService(contentService);
|
||||||
|
cleaner.setAvmNodeDAO(avmNodeDAO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
DeletionMetricsRunner metrics = new DeletionMetricsRunner();
|
||||||
|
metrics.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
setUp(true);
|
||||||
|
time("Deleting empty parent dirs");
|
||||||
|
tearDown();
|
||||||
|
|
||||||
|
setUp(false);
|
||||||
|
time("Ignoring empty parent dirs");
|
||||||
|
tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void setUp(boolean deleteEmptyDirs)
|
||||||
|
{
|
||||||
|
AuthenticationUtil.setRunAsUserSystem();
|
||||||
|
store.setDeleteEmptyDirs(deleteEmptyDirs);
|
||||||
|
deletedUrls = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void tearDown()
|
||||||
|
{
|
||||||
|
AuthenticationUtil.clearCurrentSecurityContext();
|
||||||
|
System.out.println("Deleted " + deletedUrls + " URLs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void time(String description)
|
||||||
|
{
|
||||||
|
long beforeClean = System.currentTimeMillis();
|
||||||
|
|
||||||
|
createContent();
|
||||||
|
cleanContent();
|
||||||
|
|
||||||
|
long afterClean = System.currentTimeMillis();
|
||||||
|
double timeTaken = afterClean - beforeClean;
|
||||||
|
System.out.println();
|
||||||
|
System.out.println(String.format("%s took %6.0fms", description, timeTaken));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void createContent()
|
||||||
|
{
|
||||||
|
final StoreRef storeRef = nodeService.createStore("test", "timings-" + GUID.generate());
|
||||||
|
RetryingTransactionCallback<ContentData> testCallback = new RetryingTransactionCallback<ContentData>()
|
||||||
|
{
|
||||||
|
public ContentData execute() throws Throwable
|
||||||
|
{
|
||||||
|
ContentData contentData = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < numOrphans; i++)
|
||||||
|
{
|
||||||
|
// Create some content
|
||||||
|
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
|
||||||
|
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(13);
|
||||||
|
properties.put(ContentModel.PROP_NAME, (Serializable)"test.txt");
|
||||||
|
|
||||||
|
NodeRef contentNodeRef = nodeService.createNode(
|
||||||
|
rootNodeRef,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
ContentModel.TYPE_CONTENT,
|
||||||
|
properties).getChildRef();
|
||||||
|
|
||||||
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
||||||
|
|
||||||
|
|
||||||
|
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
||||||
|
writer.putContent("INITIAL CONTENT");
|
||||||
|
|
||||||
|
contentData = writer.getContentData();
|
||||||
|
|
||||||
|
// Delete the first node, bypassing archive
|
||||||
|
nodeService.addAspect(contentNodeRef, ContentModel.ASPECT_TEMPORARY, null);
|
||||||
|
nodeService.deleteNode(contentNodeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return contentData;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void cleanContent()
|
||||||
|
{
|
||||||
|
// fire the cleaner
|
||||||
|
cleaner.setProtectDays(0);
|
||||||
|
cleaner.execute();
|
||||||
|
|
||||||
|
if (deletedUrls < numOrphans)
|
||||||
|
throw new IllegalStateException("Not all the orphans were cleaned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class CleanerListener implements ContentStoreCleanerListener
|
||||||
|
{
|
||||||
|
public void beforeDelete(ContentStore store, String contentUrl) throws ContentIOException
|
||||||
|
{
|
||||||
|
deletedUrls++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -71,6 +71,7 @@ public class FileContentStore
|
|||||||
private boolean allowRandomAccess;
|
private boolean allowRandomAccess;
|
||||||
private boolean readOnly;
|
private boolean readOnly;
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
private boolean deleteEmptyDirs = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private: for Spring-constructed instances only.
|
* Private: for Spring-constructed instances only.
|
||||||
@@ -609,6 +610,12 @@ public class FileContentStore
|
|||||||
deleted = file.delete();
|
deleted = file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete empty parents regardless of whether the file was ignore above.
|
||||||
|
if (deleteEmptyDirs && deleted)
|
||||||
|
{
|
||||||
|
deleteEmptyParents(file);
|
||||||
|
}
|
||||||
|
|
||||||
// done
|
// done
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
@@ -619,6 +626,39 @@ public class FileContentStore
|
|||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the parents of the specified file. The file itself must have been
|
||||||
|
* deleted before calling this method - since only empty directories can be deleted.
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
private void deleteEmptyParents(File file)
|
||||||
|
{
|
||||||
|
String root = getRootLocation();
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
boolean deleted = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (parent.isDirectory() && !parent.getCanonicalPath().equals(root))
|
||||||
|
{
|
||||||
|
// Only an empty directory will successfully be deleted.
|
||||||
|
deleted = parent.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException error)
|
||||||
|
{
|
||||||
|
logger.error("Unable to construct canonical path for " + parent.getAbsolutePath());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.getParentFile();
|
||||||
|
}
|
||||||
|
while(deleted);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new content URL. This must be supported by all
|
* Creates a new content URL. This must be supported by all
|
||||||
* stores that are compatible with Alfresco.
|
* stores that are compatible with Alfresco.
|
||||||
@@ -669,4 +709,14 @@ public class FileContentStore
|
|||||||
publishEvent(((ContextRefreshedEvent) event).getApplicationContext());
|
publishEvent(((ContextRefreshedEvent) event).getApplicationContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the FileContentStore to delete empty parent directories upon deleting a content URL.
|
||||||
|
*
|
||||||
|
* @param deleteEmptyDirs the deleteEmptyDirs to set
|
||||||
|
*/
|
||||||
|
public void setDeleteEmptyDirs(boolean deleteEmptyDirs)
|
||||||
|
{
|
||||||
|
this.deleteEmptyDirs = deleteEmptyDirs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,6 +50,8 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest
|
|||||||
tempDir.getAbsolutePath() +
|
tempDir.getAbsolutePath() +
|
||||||
File.separatorChar +
|
File.separatorChar +
|
||||||
getName());
|
getName());
|
||||||
|
|
||||||
|
store.setDeleteEmptyDirs(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -128,4 +130,101 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest
|
|||||||
assertTrue("Size must be positive", size > 0L);
|
assertTrue("Size must be positive", size > 0L);
|
||||||
assertTrue("Size must not be Long.MAX_VALUE", size < Long.MAX_VALUE);
|
assertTrue("Size must not be Long.MAX_VALUE", size < Long.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty parent directories should be removed when a URL is removed.
|
||||||
|
*/
|
||||||
|
public void testDeleteRemovesEmptyDirs() throws Exception
|
||||||
|
{
|
||||||
|
ContentStore store = getStore();
|
||||||
|
String url = "store://1965/12/1/13/12/file.bin";
|
||||||
|
|
||||||
|
// Ensure clean test data
|
||||||
|
if (store.exists(url)) store.delete(url);
|
||||||
|
|
||||||
|
String content = "Content for test: " + getName();
|
||||||
|
store.getWriter(new ContentContext(null, url)).putContent(content);
|
||||||
|
|
||||||
|
File root = new File(store.getRootLocation());
|
||||||
|
|
||||||
|
assertDirExists(root, "");
|
||||||
|
assertDirExists(root, "1965/12/1/13/12");
|
||||||
|
|
||||||
|
store.delete(url);
|
||||||
|
|
||||||
|
assertDirNotExists(root, "1965");
|
||||||
|
// root should be untouched.
|
||||||
|
assertDirExists(root, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only non-empty directories should be deleted.
|
||||||
|
*/
|
||||||
|
public void testDeleteLeavesNonEmptyDirs()
|
||||||
|
{
|
||||||
|
ContentStore store = getStore();
|
||||||
|
String url = "store://1965/12/1/13/12/file.bin";
|
||||||
|
|
||||||
|
// Ensure clean test data
|
||||||
|
if (store.exists(url)) store.delete(url);
|
||||||
|
|
||||||
|
String content = "Content for test: " + getName();
|
||||||
|
store.getWriter(new ContentContext(null, url)).putContent(content);
|
||||||
|
|
||||||
|
File root = new File(store.getRootLocation());
|
||||||
|
|
||||||
|
assertDirExists(root, "");
|
||||||
|
assertDirExists(root, "1965/12/1/13/12");
|
||||||
|
|
||||||
|
// Make a directory non-empty
|
||||||
|
String anotherUrl = "store://1965/12/3/another.bin";
|
||||||
|
if (store.exists(anotherUrl)) store.delete(anotherUrl);
|
||||||
|
store.getWriter(new ContentContext(null, anotherUrl));
|
||||||
|
|
||||||
|
store.delete(url);
|
||||||
|
|
||||||
|
// Parents of another.bin cannot be deleted
|
||||||
|
assertDirExists(root, "1965");
|
||||||
|
assertDirExists(root, "1965/12");
|
||||||
|
// Non-parents of another.bin could be deleted
|
||||||
|
assertDirNotExists(root, "1965/12/1");
|
||||||
|
|
||||||
|
// root should be untouched.
|
||||||
|
assertDirExists(root, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty parent directories are not deleted if the store is configured not to.
|
||||||
|
*/
|
||||||
|
public void testNoParentDirsDeleted() throws Exception
|
||||||
|
{
|
||||||
|
store.setDeleteEmptyDirs(false);
|
||||||
|
FileContentStore store = (FileContentStore) getStore();
|
||||||
|
String url = "store://1965/12/1/13/12/file.bin";
|
||||||
|
// Ensure clean test data
|
||||||
|
if (store.exists(url)) store.delete(url);
|
||||||
|
String content = "Content for test: " + getName();
|
||||||
|
store.getWriter(new ContentContext(null, url)).putContent(content);
|
||||||
|
File root = new File(store.getRootLocation());
|
||||||
|
|
||||||
|
store.delete(url);
|
||||||
|
|
||||||
|
assertDirExists(root, "1965/12/1/13/12");
|
||||||
|
// root should be untouched.
|
||||||
|
assertDirExists(root, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void assertDirExists(File root, String dir)
|
||||||
|
{
|
||||||
|
assertTrue("Directory [" + dir + "] should exist", new File(root, dir).exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void assertDirNotExists(File root, String dir)
|
||||||
|
{
|
||||||
|
assertFalse("Directory [" + dir + "] should NOT exist", new File(root, dir).exists());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -70,8 +70,6 @@ import org.apache.commons.logging.Log;
|
|||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
import com.aetrion.flickr.auth.AuthUtilities;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to house utility methods common to
|
* Helper class to house utility methods common to
|
||||||
* more than one Invite Service Web Script
|
* more than one Invite Service Web Script
|
||||||
@@ -145,7 +143,7 @@ public class InviteHelper implements InitializingBean
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addSiteMembership(invitee, siteShortName, role, inviter);
|
addSiteMembership(invitee, siteShortName, role, inviter, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,14 +266,18 @@ public class InviteHelper implements InitializingBean
|
|||||||
* @param role
|
* @param role
|
||||||
* @param runAsUser
|
* @param runAsUser
|
||||||
* @param siteService
|
* @param siteService
|
||||||
|
* @param overrideExisting
|
||||||
*/
|
*/
|
||||||
public void addSiteMembership(final String invitee, final String siteName, final String role, final String runAsUser)
|
public void addSiteMembership(final String invitee, final String siteName, final String role, final String runAsUser, final boolean overrideExisting)
|
||||||
{
|
{
|
||||||
AuthenticationUtil.runAs(new RunAsWork<Void>()
|
AuthenticationUtil.runAs(new RunAsWork<Void>()
|
||||||
{
|
{
|
||||||
public Void doWork() throws Exception
|
public Void doWork() throws Exception
|
||||||
|
{
|
||||||
|
if (overrideExisting || !siteService.isMember(siteName, invitee))
|
||||||
{
|
{
|
||||||
siteService.setMembership(siteName, invitee, role);
|
siteService.setMembership(siteName, invitee, role);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +417,7 @@ public class InviteHelper implements InitializingBean
|
|||||||
String reviewer = (String)executionVariables.get(WorkflowModelModeratedInvitation.wfVarReviewer);
|
String reviewer = (String)executionVariables.get(WorkflowModelModeratedInvitation.wfVarReviewer);
|
||||||
|
|
||||||
// Add invitee to the site
|
// Add invitee to the site
|
||||||
addSiteMembership(invitee, siteName, role, reviewer);
|
addSiteMembership(invitee, siteName, role, reviewer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@@ -18,8 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.permissions.impl.model;
|
package org.alfresco.repo.security.permissions.impl.model;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
@@ -36,8 +39,8 @@ import org.alfresco.repo.security.permissions.PermissionEntry;
|
|||||||
import org.alfresco.repo.security.permissions.PermissionReference;
|
import org.alfresco.repo.security.permissions.PermissionReference;
|
||||||
import org.alfresco.repo.security.permissions.impl.ModelDAO;
|
import org.alfresco.repo.security.permissions.impl.ModelDAO;
|
||||||
import org.alfresco.repo.security.permissions.impl.RequiredPermission;
|
import org.alfresco.repo.security.permissions.impl.RequiredPermission;
|
||||||
import org.alfresco.repo.security.permissions.impl.SimplePermissionReference;
|
|
||||||
import org.alfresco.repo.security.permissions.impl.RequiredPermission.On;
|
import org.alfresco.repo.security.permissions.impl.RequiredPermission.On;
|
||||||
|
import org.alfresco.repo.security.permissions.impl.SimplePermissionReference;
|
||||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
@@ -53,8 +56,12 @@ import org.alfresco.util.Pair;
|
|||||||
import org.dom4j.Attribute;
|
import org.dom4j.Attribute;
|
||||||
import org.dom4j.Document;
|
import org.dom4j.Document;
|
||||||
import org.dom4j.DocumentException;
|
import org.dom4j.DocumentException;
|
||||||
|
import org.dom4j.DocumentType;
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
|
import org.dom4j.io.OutputFormat;
|
||||||
import org.dom4j.io.SAXReader;
|
import org.dom4j.io.SAXReader;
|
||||||
|
import org.dom4j.io.XMLWriter;
|
||||||
|
import org.dom4j.tree.DefaultDocumentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The implementation of the model DAO Reads and stores the top level model information Encapsulates access to this
|
* The implementation of the model DAO Reads and stores the top level model information Encapsulates access to this
|
||||||
@@ -93,6 +100,8 @@ public class PermissionModel implements ModelDAO
|
|||||||
// Instance variables
|
// Instance variables
|
||||||
|
|
||||||
private String model;
|
private String model;
|
||||||
|
private String dtdSchema;
|
||||||
|
private boolean validate = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
@@ -1142,6 +1151,26 @@ public class PermissionModel implements ModelDAO
|
|||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dtd schema that is used to validate permission model
|
||||||
|
*
|
||||||
|
* @param dtdSchema
|
||||||
|
*/
|
||||||
|
public void setDtdSchema(String dtdSchema)
|
||||||
|
{
|
||||||
|
this.dtdSchema = dtdSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether model should be validated on initialization against specified dtd
|
||||||
|
*
|
||||||
|
* @param validate
|
||||||
|
*/
|
||||||
|
public void setValidate(boolean validate)
|
||||||
|
{
|
||||||
|
this.validate = validate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the dictionary service
|
* Set the dictionary service
|
||||||
*
|
*
|
||||||
@@ -1261,6 +1290,9 @@ public class PermissionModel implements ModelDAO
|
|||||||
private Document createDocument(String model)
|
private Document createDocument(String model)
|
||||||
{
|
{
|
||||||
InputStream is = this.getClass().getClassLoader().getResourceAsStream(model);
|
InputStream is = this.getClass().getClassLoader().getResourceAsStream(model);
|
||||||
|
URL dtdSchemaUrl = (dtdSchema == null)
|
||||||
|
? null
|
||||||
|
: this.getClass().getClassLoader().getResource(dtdSchema);
|
||||||
if (is == null)
|
if (is == null)
|
||||||
{
|
{
|
||||||
throw new PermissionModelException("File not found: " + model);
|
throw new PermissionModelException("File not found: " + model);
|
||||||
@@ -1268,19 +1300,154 @@ public class PermissionModel implements ModelDAO
|
|||||||
SAXReader reader = new SAXReader();
|
SAXReader reader = new SAXReader();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (validate)
|
||||||
|
{
|
||||||
|
if (dtdSchemaUrl != null)
|
||||||
|
{
|
||||||
|
is = processModelDocType(is, dtdSchemaUrl.toString());
|
||||||
|
reader.setValidation(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new PermissionModelException("Couldn't obtain DTD schema to validate permission model.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Document document = reader.read(is);
|
Document document = reader.read(is);
|
||||||
is.close();
|
is.close();
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
catch (DocumentException e)
|
catch (DocumentException e)
|
||||||
{
|
{
|
||||||
throw new PermissionModelException("Failed to create permission model document ", e);
|
throw new PermissionModelException("Failed to create permission model document: " + model, e);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
throw new PermissionModelException("Failed to close permission model document ", e);
|
throw new PermissionModelException("Failed to close permission model document: " + model, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Do something like the following so that we don't need to modify the source xml
|
||||||
|
// to validate it. The following does not work for DTDs.
|
||||||
|
|
||||||
|
// InputStream is = this.getClass().getClassLoader().getResourceAsStream(model);
|
||||||
|
// if (is == null)
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("File not found: " + model);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// InputStream dtdSchemaIs = (dtdSchema == null)
|
||||||
|
// ? null
|
||||||
|
// : this.getClass().getClassLoader().getResourceAsStream(dtdSchema);
|
||||||
|
//
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// Document document;
|
||||||
|
// SAXReader reader;
|
||||||
|
// if (validate)
|
||||||
|
// {
|
||||||
|
// if (dtdSchemaIs != null)
|
||||||
|
// {
|
||||||
|
// SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||||
|
//
|
||||||
|
// SchemaFactory schemaFactory =
|
||||||
|
// SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
|
||||||
|
//
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// factory.setSchema(schemaFactory.newSchema(
|
||||||
|
// new Source[] {new StreamSource(dtdSchemaIs)}));
|
||||||
|
// SAXParser parser = factory.newSAXParser();
|
||||||
|
// reader = new SAXReader(parser.getXMLReader());
|
||||||
|
// reader.setValidation(false);
|
||||||
|
// reader.setErrorHandler(new XMLErrorHandler());
|
||||||
|
// }
|
||||||
|
// catch (SAXException e)
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("Failed to read DTD/schema: " + dtdSchema, e);
|
||||||
|
// }
|
||||||
|
// catch (ParserConfigurationException e)
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("Failed to configure DTD/schema: " + dtdSchema, e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("Couldn't obtain DTD/schema to validate permission model.");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// reader = new SAXReader();
|
||||||
|
// }
|
||||||
|
// document = reader.read(is);
|
||||||
|
// return document;
|
||||||
|
// }
|
||||||
|
// catch (DocumentException e)
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("Failed to create permission model document: " + model, e);
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// if (is != null)
|
||||||
|
// {
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// is.close();
|
||||||
|
// }
|
||||||
|
// catch (IOException e)
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("Failed to close permission model document: " + model, e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (dtdSchemaIs != null)
|
||||||
|
// {
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// dtdSchemaIs.close();
|
||||||
|
// }
|
||||||
|
// catch (IOException e)
|
||||||
|
// {
|
||||||
|
// throw new PermissionModelException("Couldn't close DTD/schema to validate permission model.");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace or add correct DOCTYPE to the xml to allow validation against dtd
|
||||||
|
*/
|
||||||
|
private InputStream processModelDocType(InputStream is, String dtdSchemaUrl) throws DocumentException, IOException
|
||||||
|
{
|
||||||
|
SAXReader reader = new SAXReader();
|
||||||
|
// read document without validation
|
||||||
|
Document doc = reader.read(is);
|
||||||
|
DocumentType docType = doc.getDocType();
|
||||||
|
if (docType != null)
|
||||||
|
{
|
||||||
|
// replace DOCTYPE setting the full path to the xsd
|
||||||
|
docType.setSystemID(dtdSchemaUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add the DOCTYPE
|
||||||
|
docType = new DefaultDocumentType(doc.getRootElement().getName(), dtdSchemaUrl);
|
||||||
|
doc.setDocType(docType);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream fos = new ByteArrayOutputStream();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OutputFormat format = OutputFormat.createPrettyPrint(); // uses UTF-8
|
||||||
|
XMLWriter writer = new XMLWriter(fos, format);
|
||||||
|
writer.write(doc);
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ByteArrayInputStream(fos.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user