mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Fix up packaging and samples
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15043 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -119,48 +119,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!--
|
|
||||||
A ContentStore that can direct content based on the cm:storeName property.
|
|
||||||
The 'storeSelectorContentStore.constraint' ensures that values are mapped to keys in the 'storesByName' map.
|
|
||||||
-->
|
|
||||||
<bean id="storeSelectorContentStore" class="org.alfresco.repo.content.routing.StoreSelectorAspectContentStore" >
|
|
||||||
<property name="nodeService">
|
|
||||||
<ref bean="nodeService"/>
|
|
||||||
</property>
|
|
||||||
<property name="policyComponent">
|
|
||||||
<ref bean="policyComponent" />
|
|
||||||
</property>
|
|
||||||
<property name="dictionaryService">
|
|
||||||
<ref bean="dictionaryService" />
|
|
||||||
</property>
|
|
||||||
<property name="storesCache">
|
|
||||||
<ref bean="routingContentStoreCache"/>
|
|
||||||
</property>
|
|
||||||
<property name="defaultStoreName">
|
|
||||||
<value>default</value>
|
|
||||||
</property>
|
|
||||||
<property name="storesByName">
|
|
||||||
<map>
|
|
||||||
<entry key="default">
|
|
||||||
<ref bean="fileContentStore" />
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
<bean id="storeSelectorContentStore.constraint"
|
|
||||||
class="org.alfresco.repo.content.routing.StoreSelectorAspectContentStore$StoreSelectorConstraint"
|
|
||||||
init-method="initialize" >
|
|
||||||
<property name="shortName">
|
|
||||||
<value>defaultStoreSelector</value>
|
|
||||||
</property>
|
|
||||||
<property name="store">
|
|
||||||
<ref bean="storeSelectorContentStore" />
|
|
||||||
</property>
|
|
||||||
<property name="registry">
|
|
||||||
<ref bean="cm:constraintRegistry" />
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="mimetypeConfigService" class="org.alfresco.config.xml.XMLConfigService" init-method="init">
|
<bean id="mimetypeConfigService" class="org.alfresco.config.xml.XMLConfigService" init-method="init">
|
||||||
<constructor-arg>
|
<constructor-arg>
|
||||||
<bean class="org.alfresco.config.source.UrlConfigSource">
|
<bean class="org.alfresco.config.source.UrlConfigSource">
|
||||||
|
@@ -24,12 +24,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.content;
|
package org.alfresco.repo.content;
|
||||||
|
|
||||||
|
import junit.framework.Test;
|
||||||
|
import junit.framework.TestSuite;
|
||||||
|
|
||||||
import org.alfresco.repo.content.cleanup.ContentStoreCleanerTest;
|
import org.alfresco.repo.content.cleanup.ContentStoreCleanerTest;
|
||||||
import org.alfresco.repo.content.encoding.CharsetFinderTest;
|
import org.alfresco.repo.content.encoding.CharsetFinderTest;
|
||||||
import org.alfresco.repo.content.filestore.FileContentStoreTest;
|
import org.alfresco.repo.content.filestore.FileContentStoreTest;
|
||||||
import org.alfresco.repo.content.filestore.NoRandomAccessFileContentStoreTest;
|
import org.alfresco.repo.content.filestore.NoRandomAccessFileContentStoreTest;
|
||||||
import org.alfresco.repo.content.filestore.ReadOnlyFileContentStoreTest;
|
import org.alfresco.repo.content.filestore.ReadOnlyFileContentStoreTest;
|
||||||
import org.alfresco.repo.content.metadata.HtmlMetadataExtracterTest;
|
import org.alfresco.repo.content.metadata.HtmlMetadataExtracterTest;
|
||||||
|
import org.alfresco.repo.content.metadata.MailMetadataExtracterTest;
|
||||||
import org.alfresco.repo.content.metadata.MappingMetadataExtracterTest;
|
import org.alfresco.repo.content.metadata.MappingMetadataExtracterTest;
|
||||||
import org.alfresco.repo.content.metadata.OfficeMetadataExtracterTest;
|
import org.alfresco.repo.content.metadata.OfficeMetadataExtracterTest;
|
||||||
import org.alfresco.repo.content.metadata.OpenDocumentMetadataExtracterTest;
|
import org.alfresco.repo.content.metadata.OpenDocumentMetadataExtracterTest;
|
||||||
@@ -37,13 +41,12 @@ import org.alfresco.repo.content.metadata.OpenOfficeMetadataExtracterTest;
|
|||||||
import org.alfresco.repo.content.metadata.PdfBoxMetadataExtracterTest;
|
import org.alfresco.repo.content.metadata.PdfBoxMetadataExtracterTest;
|
||||||
import org.alfresco.repo.content.replication.ContentStoreReplicatorTest;
|
import org.alfresco.repo.content.replication.ContentStoreReplicatorTest;
|
||||||
import org.alfresco.repo.content.replication.ReplicatingContentStoreTest;
|
import org.alfresco.repo.content.replication.ReplicatingContentStoreTest;
|
||||||
import org.alfresco.repo.content.routing.StoreSelectorAspectContentStoreTest;
|
|
||||||
import org.alfresco.repo.content.transform.BinaryPassThroughContentTransformerTest;
|
import org.alfresco.repo.content.transform.BinaryPassThroughContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.ComplexContentTransformerTest;
|
import org.alfresco.repo.content.transform.ComplexContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.ContentTransformerRegistryTest;
|
import org.alfresco.repo.content.transform.ContentTransformerRegistryTest;
|
||||||
import org.alfresco.repo.content.transform.HtmlParserContentTransformerTest;
|
import org.alfresco.repo.content.transform.HtmlParserContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.OpenOfficeContentTransformerTest;
|
|
||||||
import org.alfresco.repo.content.transform.MailContentTransformerTest;
|
import org.alfresco.repo.content.transform.MailContentTransformerTest;
|
||||||
|
import org.alfresco.repo.content.transform.OpenOfficeContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.PdfBoxContentTransformerTest;
|
import org.alfresco.repo.content.transform.PdfBoxContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.PoiHssfContentTransformerTest;
|
import org.alfresco.repo.content.transform.PoiHssfContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.RuntimeExecutableContentTransformerTest;
|
import org.alfresco.repo.content.transform.RuntimeExecutableContentTransformerTest;
|
||||||
@@ -51,12 +54,6 @@ import org.alfresco.repo.content.transform.StringExtractingContentTransformerTes
|
|||||||
import org.alfresco.repo.content.transform.TextMiningContentTransformerTest;
|
import org.alfresco.repo.content.transform.TextMiningContentTransformerTest;
|
||||||
import org.alfresco.repo.content.transform.TextToPdfContentTransformerTest;
|
import org.alfresco.repo.content.transform.TextToPdfContentTransformerTest;
|
||||||
|
|
||||||
// TODO: This class is currently missing
|
|
||||||
// import org.alfresco.repo.content.transform.TextToPdfContentTransformerTest;
|
|
||||||
|
|
||||||
import junit.framework.Test;
|
|
||||||
import junit.framework.TestSuite;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suite for content-related tests.
|
* Suite for content-related tests.
|
||||||
*
|
*
|
||||||
@@ -64,6 +61,7 @@ import junit.framework.TestSuite;
|
|||||||
*/
|
*/
|
||||||
public class ContentTestSuite extends TestSuite
|
public class ContentTestSuite extends TestSuite
|
||||||
{
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static Test suite()
|
public static Test suite()
|
||||||
{
|
{
|
||||||
TestSuite suite = new TestSuite();
|
TestSuite suite = new TestSuite();
|
||||||
@@ -75,6 +73,7 @@ public class ContentTestSuite extends TestSuite
|
|||||||
suite.addTestSuite(ReadOnlyFileContentStoreTest.class);
|
suite.addTestSuite(ReadOnlyFileContentStoreTest.class);
|
||||||
suite.addTestSuite(MappingMetadataExtracterTest.class);
|
suite.addTestSuite(MappingMetadataExtracterTest.class);
|
||||||
suite.addTestSuite(HtmlMetadataExtracterTest.class);
|
suite.addTestSuite(HtmlMetadataExtracterTest.class);
|
||||||
|
suite.addTestSuite(MailMetadataExtracterTest.class);
|
||||||
suite.addTestSuite(OfficeMetadataExtracterTest.class);
|
suite.addTestSuite(OfficeMetadataExtracterTest.class);
|
||||||
suite.addTestSuite(OpenDocumentMetadataExtracterTest.class);
|
suite.addTestSuite(OpenDocumentMetadataExtracterTest.class);
|
||||||
suite.addTestSuite(OpenOfficeMetadataExtracterTest.class);
|
suite.addTestSuite(OpenOfficeMetadataExtracterTest.class);
|
||||||
@@ -97,7 +96,17 @@ public class ContentTestSuite extends TestSuite
|
|||||||
suite.addTestSuite(MimetypeMapTest.class);
|
suite.addTestSuite(MimetypeMapTest.class);
|
||||||
suite.addTestSuite(RoutingContentServiceTest.class);
|
suite.addTestSuite(RoutingContentServiceTest.class);
|
||||||
suite.addTestSuite(RoutingContentStoreTest.class);
|
suite.addTestSuite(RoutingContentStoreTest.class);
|
||||||
suite.addTestSuite(StoreSelectorAspectContentStoreTest.class);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class clazz = Class.forName("org.alfresco.repo.content.routing.StoreSelectorAspectContentStoreTest");
|
||||||
|
suite.addTestSuite(clazz);
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
@@ -1,440 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
* This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
|
|
||||||
* As a special exception to the terms and conditions of version 2.0 of
|
|
||||||
* the GPL, you may redistribute this Program in connection with Free/Libre
|
|
||||||
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
|
||||||
* FLOSS exception. You should have recieved a copy of the text describing
|
|
||||||
* the FLOSS exception, and it is also available here:
|
|
||||||
* http://www.alfresco.com/legal/licensing"
|
|
||||||
*/
|
|
||||||
package org.alfresco.repo.content.routing;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
|
||||||
import org.alfresco.model.ContentModel;
|
|
||||||
import org.alfresco.repo.content.AbstractRoutingContentStore;
|
|
||||||
import org.alfresco.repo.content.ContentContext;
|
|
||||||
import org.alfresco.repo.content.ContentStore;
|
|
||||||
import org.alfresco.repo.content.NodeContentContext;
|
|
||||||
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
|
|
||||||
import org.alfresco.repo.node.NodeServicePolicies;
|
|
||||||
import org.alfresco.repo.policy.JavaBehaviour;
|
|
||||||
import org.alfresco.repo.policy.PolicyComponent;
|
|
||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|
||||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
|
||||||
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
|
||||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
||||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
|
||||||
import org.alfresco.service.cmr.repository.ContentData;
|
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
|
||||||
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.datatype.DefaultTypeConverter;
|
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
|
||||||
import org.alfresco.service.namespace.QName;
|
|
||||||
import org.alfresco.util.EqualsHelper;
|
|
||||||
import org.alfresco.util.PropertyCheck;
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of a {@link AbstractRoutingContentStore routing content store} that diverts
|
|
||||||
* and moves content based on the <b>cm:storeSelector</b> aspect.
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public class StoreSelectorAspectContentStore
|
|
||||||
extends AbstractRoutingContentStore
|
|
||||||
implements InitializingBean,
|
|
||||||
NodeServicePolicies.OnUpdatePropertiesPolicy,
|
|
||||||
NodeServicePolicies.OnAddAspectPolicy
|
|
||||||
{
|
|
||||||
private static final String ERR_INVALID_DEFAULT_STORE = "content.routing.err.invalid_default_store";
|
|
||||||
private static final String KEY_CONTENT_MOVE_DETAILS = "StoreSelectorAspectContentStore.ContentMoveDetails";
|
|
||||||
|
|
||||||
private static Log logger = LogFactory.getLog(StoreSelectorAspectContentStore.class);
|
|
||||||
|
|
||||||
private ContentMoveTransactionListener transactionListener;
|
|
||||||
private NodeService nodeService;
|
|
||||||
private PolicyComponent policyComponent;
|
|
||||||
private DictionaryService dictionaryService;
|
|
||||||
private Map<String, ContentStore> storesByName;
|
|
||||||
private List<ContentStore> stores;
|
|
||||||
private String defaultStoreName;
|
|
||||||
|
|
||||||
public StoreSelectorAspectContentStore()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param nodeService the service to access the properties
|
|
||||||
*/
|
|
||||||
public void setNodeService(NodeService nodeService)
|
|
||||||
{
|
|
||||||
this.nodeService = nodeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param policyComponent register to receive updates to the <b>cm:storeSelector</b> aspect
|
|
||||||
*/
|
|
||||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
|
||||||
{
|
|
||||||
this.policyComponent = policyComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dictionaryService used to check for content property types
|
|
||||||
*/
|
|
||||||
public void setDictionaryService(DictionaryService dictionaryService)
|
|
||||||
{
|
|
||||||
this.dictionaryService = dictionaryService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param storesByName a map of content stores keyed by a common name
|
|
||||||
*/
|
|
||||||
public void setStoresByName(Map<String, ContentStore> storesByName)
|
|
||||||
{
|
|
||||||
this.storesByName = storesByName;
|
|
||||||
this.stores = new ArrayList<ContentStore>(storesByName.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Returns the stores keyed by store name
|
|
||||||
*/
|
|
||||||
public Map<String, ContentStore> getStoresByName()
|
|
||||||
{
|
|
||||||
return storesByName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the name of the store to select if the content being created is not associated
|
|
||||||
* with any specific value in the <b>cm:storeSelector</b> or if the aspect is not
|
|
||||||
* present.
|
|
||||||
*
|
|
||||||
* @param defaultStoreName the name of one of the stores
|
|
||||||
*
|
|
||||||
* @see #setStoresByName(Map)
|
|
||||||
*/
|
|
||||||
public void setDefaultStoreName(String defaultStoreName)
|
|
||||||
{
|
|
||||||
this.defaultStoreName = defaultStoreName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks that the required properties are present
|
|
||||||
*/
|
|
||||||
public void afterPropertiesSet() throws Exception
|
|
||||||
{
|
|
||||||
PropertyCheck.mandatory(this, "nodeService", nodeService);
|
|
||||||
PropertyCheck.mandatory(this, "storesByName", storesByName);
|
|
||||||
PropertyCheck.mandatory(this, "defaultStoreName", defaultStoreName);
|
|
||||||
// Check that the default store name is valid
|
|
||||||
if (storesByName.get(defaultStoreName) == null)
|
|
||||||
{
|
|
||||||
AlfrescoRuntimeException.create(ERR_INVALID_DEFAULT_STORE, defaultStoreName, storesByName.keySet());
|
|
||||||
}
|
|
||||||
// Register to receive change updates relevant to the aspect
|
|
||||||
// Register to receive property change updates
|
|
||||||
policyComponent.bindClassBehaviour(
|
|
||||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"),
|
|
||||||
ContentModel.ASPECT_STORE_SELECTOR,
|
|
||||||
new JavaBehaviour(this, "onAddAspect"));
|
|
||||||
policyComponent.bindClassBehaviour(
|
|
||||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
|
|
||||||
ContentModel.ASPECT_STORE_SELECTOR,
|
|
||||||
new JavaBehaviour(this, "onUpdateProperties"));
|
|
||||||
|
|
||||||
// Construct the transaction listener that will be bound in
|
|
||||||
transactionListener = new ContentMoveTransactionListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<ContentStore> getAllStores()
|
|
||||||
{
|
|
||||||
return stores;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ContentStore selectWriteStore(ContentContext ctx)
|
|
||||||
{
|
|
||||||
ContentStore store;
|
|
||||||
String storeNameProp;
|
|
||||||
if (!(ctx instanceof NodeContentContext))
|
|
||||||
{
|
|
||||||
storeNameProp = "<NodeRef not available>";
|
|
||||||
store = storesByName.get(defaultStoreName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NodeRef nodeRef = ((NodeContentContext) ctx).getNodeRef(); // Never null
|
|
||||||
storeNameProp = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_STORE_NAME);
|
|
||||||
if (storeNameProp == null)
|
|
||||||
{
|
|
||||||
storeNameProp = "<null>";
|
|
||||||
store = storesByName.get(defaultStoreName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
store = storesByName.get(storeNameProp);
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
// There was no store with that name
|
|
||||||
storeNameProp = "<unmapped store: " + storeNameProp + ">";
|
|
||||||
store = storesByName.get(defaultStoreName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Done
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(
|
|
||||||
"ContentStore selected: \n" +
|
|
||||||
" Node context: " + ctx + "\n" +
|
|
||||||
" Store name: " + storeNameProp + "\n" +
|
|
||||||
" Store Selected: " + store);
|
|
||||||
}
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to select a store, taking into account <tt>null</tt> and invalid values.
|
|
||||||
*/
|
|
||||||
private ContentStore selectStore(String storeName)
|
|
||||||
{
|
|
||||||
if (storeName == null || !storesByName.containsKey(storeName))
|
|
||||||
{
|
|
||||||
storeName = defaultStoreName;
|
|
||||||
}
|
|
||||||
return storesByName.get(storeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to carry info into the post-transaction phase
|
|
||||||
*/
|
|
||||||
private static class ContentMoveDetail
|
|
||||||
{
|
|
||||||
private final ContentStore oldStore;
|
|
||||||
private final ContentStore newStore;
|
|
||||||
private final String contentUrl;
|
|
||||||
private ContentMoveDetail(ContentStore oldStore, ContentStore newStore, String contentUrl)
|
|
||||||
{
|
|
||||||
this.oldStore = oldStore;
|
|
||||||
this.newStore = newStore;
|
|
||||||
this.contentUrl = contentUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Ensures that the content is copied between stores only if the transaction is successful.
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
private class ContentMoveTransactionListener extends TransactionListenerAdapter
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void afterCommit()
|
|
||||||
{
|
|
||||||
List<ContentMoveDetail> contentMoveDetails = TransactionalResourceHelper.getList(KEY_CONTENT_MOVE_DETAILS);
|
|
||||||
for (ContentMoveDetail contentMoveDetail : contentMoveDetails)
|
|
||||||
{
|
|
||||||
moveContent(contentMoveDetail.oldStore, contentMoveDetail.newStore, contentMoveDetail.contentUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move content from the old store to the new store
|
|
||||||
*/
|
|
||||||
private void scheduleContentMove(ContentStore oldStore, ContentStore newStore, String contentUrl)
|
|
||||||
{
|
|
||||||
// Add the details of the copy to the transaction
|
|
||||||
List<ContentMoveDetail> contentMoveDetails = TransactionalResourceHelper.getList(KEY_CONTENT_MOVE_DETAILS);
|
|
||||||
ContentMoveDetail detail = new ContentMoveDetail(oldStore, newStore, contentUrl);
|
|
||||||
contentMoveDetails.add(detail);
|
|
||||||
// Bind the listener to the transaction
|
|
||||||
AlfrescoTransactionSupport.bindListener(transactionListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveContent(ContentStore oldStore, ContentStore newStore, String contentUrl)
|
|
||||||
{
|
|
||||||
ContentReader reader = oldStore.getReader(contentUrl);
|
|
||||||
if (!reader.exists())
|
|
||||||
{
|
|
||||||
// Nothing to copy
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ContentContext ctx = new ContentContext(null, contentUrl);
|
|
||||||
ContentWriter writer = newStore.getWriter(ctx);
|
|
||||||
// Copy it
|
|
||||||
writer.putContent(reader);
|
|
||||||
// Remove the old content
|
|
||||||
oldStore.delete(contentUrl);
|
|
||||||
// Done
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(
|
|
||||||
"Store selector moved content: \n" +
|
|
||||||
" Old store: " + oldStore + "\n" +
|
|
||||||
" New Store: " + newStore + "\n" +
|
|
||||||
" Content: " + contentUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that all content is moved to the correct store.
|
|
||||||
* <p>
|
|
||||||
* Spoofs a call to {@link #onUpdateProperties(NodeRef, Map, Map)}.
|
|
||||||
*/
|
|
||||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
|
||||||
{
|
|
||||||
Map<QName, Serializable> after = nodeService.getProperties(nodeRef);
|
|
||||||
// Pass the call through. It is only interested in a single property.
|
|
||||||
onUpdateProperties(
|
|
||||||
nodeRef,
|
|
||||||
Collections.<QName, Serializable>emptyMap(),
|
|
||||||
after);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps the content in the correct store based on changes to the <b>cm:storeName</b> property
|
|
||||||
*/
|
|
||||||
public void onUpdateProperties(
|
|
||||||
NodeRef nodeRef,
|
|
||||||
Map<QName, Serializable> before,
|
|
||||||
Map<QName, Serializable> after)
|
|
||||||
{
|
|
||||||
String storeNameBefore = (String) before.get(ContentModel.PROP_STORE_NAME);
|
|
||||||
String storeNameAfter = (String) after.get(ContentModel.PROP_STORE_NAME);
|
|
||||||
if (EqualsHelper.nullSafeEquals(storeNameBefore, storeNameAfter))
|
|
||||||
{
|
|
||||||
// We're not interested in the change
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Find out which store to move the content to
|
|
||||||
ContentStore oldStore = selectStore(storeNameBefore);
|
|
||||||
ContentStore newStore = selectStore(storeNameAfter);
|
|
||||||
// Don't do anything if the store did not change
|
|
||||||
if (oldStore == newStore)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Find all content properties and move the content
|
|
||||||
List<String> contentUrls = new ArrayList<String>(1);
|
|
||||||
for (QName propertyQName : after.keySet())
|
|
||||||
{
|
|
||||||
PropertyDefinition propDef = dictionaryService.getProperty(propertyQName);
|
|
||||||
if (propDef == null)
|
|
||||||
{
|
|
||||||
// Ignore
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!propDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))
|
|
||||||
{
|
|
||||||
// It is not content
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// The property value
|
|
||||||
Serializable propertyValue = after.get(propertyQName);
|
|
||||||
if (propertyValue == null)
|
|
||||||
{
|
|
||||||
// Ignore missing values
|
|
||||||
}
|
|
||||||
// Get the content URLs, being sensitive to collections
|
|
||||||
if (propDef.isMultiValued())
|
|
||||||
{
|
|
||||||
Collection<ContentData> contentValues =
|
|
||||||
DefaultTypeConverter.INSTANCE.getCollection(ContentData.class, propertyValue);
|
|
||||||
if (contentValues.size() == 0)
|
|
||||||
{
|
|
||||||
// No content
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (ContentData contentValue : contentValues)
|
|
||||||
{
|
|
||||||
String contentUrl = contentValue.getContentUrl();
|
|
||||||
if (contentUrl != null)
|
|
||||||
{
|
|
||||||
contentUrls.add(contentUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ContentData contentValue = DefaultTypeConverter.INSTANCE.convert(ContentData.class, propertyValue);
|
|
||||||
String contentUrl = contentValue.getContentUrl();
|
|
||||||
if (contentUrl != null)
|
|
||||||
{
|
|
||||||
contentUrls.add(contentUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move content from the old store to the new store
|
|
||||||
for (String contentUrl : contentUrls)
|
|
||||||
{
|
|
||||||
scheduleContentMove(oldStore, newStore, contentUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A constraint that acts as a list of values, where the values are the store names
|
|
||||||
* injected into the {@link StoreSelectorAspectContentStore}.
|
|
||||||
* <p>
|
|
||||||
* If the store is not active or is incorrectly configured, then this constraint
|
|
||||||
* will contain a single value of 'Default'. Any attempt to set another value will
|
|
||||||
* lead to constraint failures.
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public static class StoreSelectorConstraint extends ListOfValuesConstraint
|
|
||||||
{
|
|
||||||
private StoreSelectorAspectContentStore store;
|
|
||||||
/**
|
|
||||||
* Required default constructor
|
|
||||||
*/
|
|
||||||
public StoreSelectorConstraint()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStore(StoreSelectorAspectContentStore store)
|
|
||||||
{
|
|
||||||
this.store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize()
|
|
||||||
{
|
|
||||||
checkPropertyNotNull("store", store);
|
|
||||||
List<String> allowedValues = new ArrayList<String>(store.getStoresByName().keySet());
|
|
||||||
super.setAllowedValues(allowedValues);
|
|
||||||
// Now initialize as we have set the LOV
|
|
||||||
super.initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,287 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
* This program 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 General Public License for more details.
|
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
|
|
||||||
* As a special exception to the terms and conditions of version 2.0 of
|
|
||||||
* the GPL, you may redistribute this Program in connection with Free/Libre
|
|
||||||
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
|
||||||
* FLOSS exception. You should have recieved a copy of the text describing
|
|
||||||
* the FLOSS exception, and it is also available here:
|
|
||||||
* http://www.alfresco.com/legal/licensing"
|
|
||||||
*/
|
|
||||||
package org.alfresco.repo.content.routing;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
|
||||||
import org.alfresco.repo.content.ContentServiceImpl;
|
|
||||||
import org.alfresco.repo.content.ContentStore;
|
|
||||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
|
||||||
import org.alfresco.repo.content.routing.StoreSelectorAspectContentStore.StoreSelectorConstraint;
|
|
||||||
import org.alfresco.repo.node.integrity.IntegrityException;
|
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
||||||
import org.alfresco.service.ServiceRegistry;
|
|
||||||
import org.alfresco.service.cmr.model.FileFolderService;
|
|
||||||
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.transaction.TransactionService;
|
|
||||||
import org.alfresco.util.ApplicationContextHelper;
|
|
||||||
import org.alfresco.util.TempFileProvider;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link StoreSelectorAspectContentStore}
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public class StoreSelectorAspectContentStoreTest extends TestCase
|
|
||||||
{
|
|
||||||
private static final String STORE_ONE = "Store1";
|
|
||||||
private static final String STORE_TWO = "Store2";
|
|
||||||
private static final String STORE_THREE = "Store3";
|
|
||||||
|
|
||||||
private static ConfigurableApplicationContext ctx =
|
|
||||||
(ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
|
|
||||||
|
|
||||||
private TransactionService transactionService;
|
|
||||||
private NodeService nodeService;
|
|
||||||
private FileFolderService fileFolderService;
|
|
||||||
|
|
||||||
private Map<String, ContentStore> storesByName;
|
|
||||||
private FileContentStore fileStore1;
|
|
||||||
private FileContentStore fileStore2;
|
|
||||||
private FileContentStore fileStore3;
|
|
||||||
private StoreSelectorAspectContentStore store;
|
|
||||||
private NodeRef contentNodeRef;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
|
||||||
transactionService = serviceRegistry.getTransactionService();
|
|
||||||
nodeService = serviceRegistry.getNodeService();
|
|
||||||
fileFolderService = serviceRegistry.getFileFolderService();
|
|
||||||
|
|
||||||
AuthenticationUtil.pushAuthentication();
|
|
||||||
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
|
||||||
|
|
||||||
fileStore1 = new FileContentStore(
|
|
||||||
ctx,
|
|
||||||
TempFileProvider.getSystemTempDir() + "/fileStore1");
|
|
||||||
fileStore2 = new FileContentStore(
|
|
||||||
ctx,
|
|
||||||
TempFileProvider.getSystemTempDir() + "/fileStore2");
|
|
||||||
fileStore3 = new FileContentStore(
|
|
||||||
ctx,
|
|
||||||
TempFileProvider.getSystemTempDir() + "/fileStore3");
|
|
||||||
|
|
||||||
storesByName = new HashMap<String, ContentStore>(7);
|
|
||||||
storesByName.put(STORE_ONE, fileStore1);
|
|
||||||
storesByName.put(STORE_TWO, fileStore2);
|
|
||||||
storesByName.put(STORE_THREE, fileStore3);
|
|
||||||
|
|
||||||
store = (StoreSelectorAspectContentStore) ctx.getBean("storeSelectorContentStore");
|
|
||||||
store.setStoresByName(storesByName);
|
|
||||||
store.setDefaultStoreName(STORE_ONE);
|
|
||||||
store.afterPropertiesSet();
|
|
||||||
|
|
||||||
// Force the constraint to re-initialize
|
|
||||||
StoreSelectorConstraint storeConstraint = (StoreSelectorConstraint) ctx.getBean("storeSelectorContentStore.constraint");
|
|
||||||
storeConstraint.initialize();
|
|
||||||
|
|
||||||
// Change the content service's default store
|
|
||||||
ContentServiceImpl contentService = (ContentServiceImpl) ctx.getBean("contentService");
|
|
||||||
contentService.setStore(store);
|
|
||||||
|
|
||||||
// Create a content node
|
|
||||||
RetryingTransactionCallback<NodeRef> makeNodeCallback = new RetryingTransactionCallback<NodeRef>()
|
|
||||||
{
|
|
||||||
public NodeRef execute() throws Throwable
|
|
||||||
{
|
|
||||||
StoreRef storeRef = nodeService.createStore(
|
|
||||||
StoreRef.PROTOCOL_TEST,
|
|
||||||
getName() + "_" + System.currentTimeMillis());
|
|
||||||
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
|
|
||||||
// Create a folder
|
|
||||||
NodeRef folderNodeRef = nodeService.createNode(
|
|
||||||
rootNodeRef,
|
|
||||||
ContentModel.ASSOC_CHILDREN,
|
|
||||||
ContentModel.ASSOC_CHILDREN,
|
|
||||||
ContentModel.TYPE_FOLDER).getChildRef();
|
|
||||||
// Add some content
|
|
||||||
return fileFolderService.create(
|
|
||||||
folderNodeRef,
|
|
||||||
getName() + ".txt",
|
|
||||||
ContentModel.TYPE_CONTENT).getNodeRef();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
contentNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(makeNodeCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tearDown() throws Exception
|
|
||||||
{
|
|
||||||
AuthenticationUtil.popAuthentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes to the file
|
|
||||||
* @return Returns the new content URL
|
|
||||||
*/
|
|
||||||
private String writeToFile()
|
|
||||||
{
|
|
||||||
RetryingTransactionCallback<String> writeContentCallback = new RetryingTransactionCallback<String>()
|
|
||||||
{
|
|
||||||
public String execute() throws Throwable
|
|
||||||
{
|
|
||||||
ContentWriter writer = fileFolderService.getWriter(contentNodeRef);
|
|
||||||
writer.putContent("Some test content");
|
|
||||||
return writer.getContentUrl();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return transactionService.getRetryingTransactionHelper().doInTransaction(writeContentCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the name of the store that must hold the content
|
|
||||||
* @param storeName the name of the store
|
|
||||||
*/
|
|
||||||
private void setStoreNameProperty(String storeName)
|
|
||||||
{
|
|
||||||
// The nodeService is transactional
|
|
||||||
nodeService.setProperty(contentNodeRef, ContentModel.PROP_STORE_NAME, storeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that a <tt>null</tt> <b>cm:storeName</b> property is acceptable.
|
|
||||||
*/
|
|
||||||
public void testNullStoreNameProperty() throws Exception
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setStoreNameProperty(null);
|
|
||||||
}
|
|
||||||
catch (Throwable e)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to set store name property to null", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that an invalid <b>cm:storeName</b> property is kicked out.
|
|
||||||
*/
|
|
||||||
public void testInvalidStoreNameProperty() throws Exception
|
|
||||||
{
|
|
||||||
RetryingTransactionCallback<Object> setInvalidStoreNameCallback = new RetryingTransactionCallback<Object>()
|
|
||||||
{
|
|
||||||
public Object execute() throws Throwable
|
|
||||||
{
|
|
||||||
setStoreNameProperty("bogus");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try
|
|
||||||
{
|
|
||||||
transactionService.getRetryingTransactionHelper().doInTransaction(setInvalidStoreNameCallback, false, true);
|
|
||||||
setStoreNameProperty("bogus");
|
|
||||||
fail("Expected integrity error for bogus store name");
|
|
||||||
}
|
|
||||||
catch (IntegrityException e)
|
|
||||||
{
|
|
||||||
// Expected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that the default store is used if the property is not set
|
|
||||||
*/
|
|
||||||
public void testWriteWithoutAspect() throws Exception
|
|
||||||
{
|
|
||||||
String contentUrl = writeToFile();
|
|
||||||
// The content should be in the default store
|
|
||||||
assertTrue("Default store does not have content", fileStore1.exists(contentUrl));
|
|
||||||
assertFalse("Mapped store should not have content", fileStore2.exists(contentUrl));
|
|
||||||
assertFalse("Mapped store should not have content", fileStore3.exists(contentUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSimpleWritesWithAspect() throws Exception
|
|
||||||
{
|
|
||||||
for (Map.Entry<String, ContentStore> entry : storesByName.entrySet())
|
|
||||||
{
|
|
||||||
String storeName = entry.getKey();
|
|
||||||
ContentStore store = entry.getValue();
|
|
||||||
setStoreNameProperty(storeName);
|
|
||||||
String contentUrl = writeToFile();
|
|
||||||
assertTrue("Content not in store " + storeName, store.exists(contentUrl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPropertyChange() throws Exception
|
|
||||||
{
|
|
||||||
setStoreNameProperty(STORE_ONE);
|
|
||||||
String contentUrl = writeToFile();
|
|
||||||
assertTrue("Store1 should have content", storesByName.get(STORE_ONE).exists(contentUrl));
|
|
||||||
assertFalse("Store2 should NOT have content", storesByName.get(STORE_TWO).exists(contentUrl));
|
|
||||||
// Change the property
|
|
||||||
setStoreNameProperty(STORE_TWO);
|
|
||||||
// It should have moved
|
|
||||||
assertFalse("Store1 should NOT have content", storesByName.get(STORE_ONE).exists(contentUrl));
|
|
||||||
assertTrue("Store2 should have content", storesByName.get(STORE_TWO).exists(contentUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that the store move does not occur if an invalid <b>cm:storeName</b> property us used.
|
|
||||||
*/
|
|
||||||
public void testPropertyChangeWithIntegrityError() throws Exception
|
|
||||||
{
|
|
||||||
// setStoreNameProperty(STORE_ONE);
|
|
||||||
String contentUrl = writeToFile();
|
|
||||||
assertTrue("Store1 should have content", storesByName.get(STORE_ONE).exists(contentUrl));
|
|
||||||
assertFalse("Store2 should NOT have content", storesByName.get(STORE_TWO).exists(contentUrl));
|
|
||||||
// Change the property
|
|
||||||
setStoreNameProperty(STORE_TWO);
|
|
||||||
// It should have moved
|
|
||||||
assertFalse("Store1 should NOT have content", storesByName.get(STORE_ONE).exists(contentUrl));
|
|
||||||
assertTrue("Store2 should have content", storesByName.get(STORE_TWO).exists(contentUrl));
|
|
||||||
RetryingTransactionCallback<Object> setInvalidStoreNameCallback = new RetryingTransactionCallback<Object>()
|
|
||||||
{
|
|
||||||
public Object execute() throws Throwable
|
|
||||||
{
|
|
||||||
setStoreNameProperty("bogus");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try
|
|
||||||
{
|
|
||||||
transactionService.getRetryingTransactionHelper().doInTransaction(setInvalidStoreNameCallback, false, true);
|
|
||||||
fail("Expected integrity error for bogus store name");
|
|
||||||
}
|
|
||||||
catch (IntegrityException e)
|
|
||||||
{
|
|
||||||
// Expected
|
|
||||||
}
|
|
||||||
assertTrue("Store2 should have content", storesByName.get(STORE_TWO).exists(contentUrl));
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user