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:
Dave Ward
2011-08-18 11:28:44 +00:00
parent b9b352479e
commit 27dfd5ab17
9 changed files with 566 additions and 22 deletions

View File

@@ -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" />

View File

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

View File

@@ -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:;.*

View File

@@ -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>
* *

View File

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

View File

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

View File

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

View File

@@ -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")

View File

@@ -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());
} }
/** /**