diff --git a/config/alfresco/extension/replicating-content-services-context.xml.sample b/config/alfresco/extension/replicating-content-services-context.xml.sample index 9e53220166..40a39b7d92 100644 --- a/config/alfresco/extension/replicating-content-services-context.xml.sample +++ b/config/alfresco/extension/replicating-content-services-context.xml.sample @@ -74,9 +74,6 @@ false - - - diff --git a/source/java/org/alfresco/repo/content/AbstractContentReader.java b/source/java/org/alfresco/repo/content/AbstractContentReader.java index 175aed0de2..986b79dc7c 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentReader.java +++ b/source/java/org/alfresco/repo/content/AbstractContentReader.java @@ -55,7 +55,8 @@ import org.springframework.util.FileCopyUtils; * Implements all the convenience methods of the interface. The only methods * that need to be implemented, i.e. provide low-level content access are: * * * @author Derek Hulley diff --git a/source/java/org/alfresco/repo/content/AbstractContentStore.java b/source/java/org/alfresco/repo/content/AbstractContentStore.java index 67537fc4a5..c1e4fc9ce1 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentStore.java +++ b/source/java/org/alfresco/repo/content/AbstractContentStore.java @@ -31,6 +31,7 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.util.GUID; /** @@ -45,16 +46,6 @@ import org.alfresco.util.GUID; */ public abstract class AbstractContentStore implements ContentStore { - /** - * Simple implementation that uses the - * {@link ContentReader#exists() reader's exists} method as its implementation. - */ - public boolean exists(String contentUrl) throws ContentIOException - { - ContentReader reader = getReader(contentUrl); - return reader.exists(); - } - /** * Creates a new content URL. This must be supported by all * stores that are compatible with Alfresco. @@ -126,8 +117,33 @@ public abstract class AbstractContentStore implements ContentStore return path; } + /** + * Simple implementation that uses the + * {@link ContentReader#exists() reader's exists} method as its implementation. + */ + public boolean exists(String contentUrl) throws ContentIOException + { + ContentReader reader = getReader(contentUrl); + return reader.exists(); + } + + /** + * Searches for URLs using null dates. + * + * @see ContentStore#getUrls(java.util.Date, java.util.Date) + */ public final Set getUrls() throws ContentIOException { return getUrls(null, null); } + + /** + * @see ContentContext + * @see ContentStore#getWriter(ContentContext) + */ + public final ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException + { + ContentContext ctx = new ContentContext(existingContentReader, newContentUrl); + return getWriter(ctx); + } } diff --git a/source/java/org/alfresco/repo/content/AbstractContentWriter.java b/source/java/org/alfresco/repo/content/AbstractContentWriter.java index eb0f5fb743..22e2e68749 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentWriter.java +++ b/source/java/org/alfresco/repo/content/AbstractContentWriter.java @@ -55,6 +55,7 @@ import org.springframework.util.FileCopyUtils; * Implements all the convenience methods of the interface. The only methods * that need to be implemented, i.e. provide low-level content access are: * * diff --git a/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java b/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java new file mode 100644 index 0000000000..481719e021 --- /dev/null +++ b/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2005-2007 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; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A store providing support for content store implementations that provide + * routing of content read and write requests based on context. + * + * @see ContentContext + * + * @since 2.1 + * @author Derek Hulley + */ +public abstract class AbstractRoutingContentStore implements ContentStore +{ + private static Log logger = LogFactory.getLog(AbstractRoutingContentStore.class); + + private SimpleCache storesByContentUrl; + private ReadLock storesCacheReadLock; + private WriteLock storesCacheWriteLock; + + protected AbstractRoutingContentStore() + { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + storesCacheReadLock = lock.readLock(); + storesCacheWriteLock = lock.writeLock(); + } + + /** + * @param storesCache cache of stores used to access URLs + */ + public void setStoresCache(SimpleCache storesCache) + { + this.storesByContentUrl = storesCache; + } + + /** + * @return Returns a list of all possible stores available for reading or writing + */ + protected abstract List getAllStores(); + + /** + * Get a content store based on the context provided. The applicability of the + * context and even the types of context allowed are up to the implementation, but + * normally there should be a fallback case for when the parameters are not adequate + * to make a decision. + * + * @param ctx the context to use to make the choice + * @return Returns the store most appropriate for the given context + */ + protected abstract ContentStore selectWriteStore(ContentContext ctx); + + /** + * Checks the cache for the store and ensures that the URL is in the store. + * + * @param contentUrl the content URL to search for + * @return Returns the store matching the content URL + */ + private ContentStore selectReadStore(String contentUrl) + { + storesCacheReadLock.lock(); + try + { + // Check if the store is in the cache + ContentStore store = storesByContentUrl.get(contentUrl); + if (store != null && store.exists(contentUrl)) + { + // We found a store and can use it + return store; + } + } + finally + { + storesCacheReadLock.unlock(); + } + // Get the write lock and double check + storesCacheWriteLock.lock(); + try + { + // Double check + ContentStore store = storesByContentUrl.get(contentUrl); + if (store != null && store.exists(contentUrl)) + { + // We found a store and can use it + if (logger.isDebugEnabled()) + { + logger.debug( + "Found mapped store for content URL: \n" + + " Content URL: " + contentUrl + "\n" + + " Store: " + store); + } + return store; + } + // It isn't, so search all the stores + List stores = getAllStores(); + for (ContentStore storeInList : stores) + { + if (!store.exists(contentUrl)) + { + // It is not in the store + continue; + } + // We found one + store = storeInList; + // Put the value in the cache + storesByContentUrl.put(contentUrl, store); + break; + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Mapped content URL to store for reading: \n" + + " Content URL: " + contentUrl + "\n" + + " Store: " + store); + } + return store; + } + finally + { + storesCacheWriteLock.unlock(); + } + } + + /** + * This operation has to be performed on all the stores in order to maintain the + * {@link ContentStore#exists(String)} contract. + */ + public boolean delete(String contentUrl) throws ContentIOException + { + boolean deleted = true; + List stores = getAllStores(); + for (ContentStore store : stores) + { + deleted &= store.delete(contentUrl); + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted content URL from stores: \n" + + " Stores: " + stores.size() + "\n" + + " Deleted: " + deleted); + } + return deleted; + } + + /** + * @see #selectReadStore(String) + */ + public boolean exists(String contentUrl) throws ContentIOException + { + ContentStore store = selectReadStore(contentUrl); + return (store != null); + } + + /** + * @return Returns a valid reader from one of the stores otherwise + * a {@link EmptyContentReader} is returned. + */ + public ContentReader getReader(String contentUrl) throws ContentIOException + { + ContentStore store = selectReadStore(contentUrl); + if (store != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Getting reader from store: \n" + + " Content URL: " + contentUrl + "\n" + + " Store: " + store); + } + return store.getReader(contentUrl); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Getting empty reader for content URL: " + contentUrl); + } + return new EmptyContentReader(contentUrl); + } + } + + /** + * Compile a set of URLs from all stores. + */ + public Set getUrls() throws ContentIOException + { + Set urls = new HashSet(1139); + List stores = getAllStores(); + for (ContentStore store : stores) + { + Set storeUrls = store.getUrls(); + urls.addAll(storeUrls); + } + if (logger.isDebugEnabled()) + { + logger.debug("Found " + urls.size() + " URLs from " + stores.size() + " stores"); + } + return urls; + } + + /** + * Compile a set of URLs from all stores given the date range. + */ + public Set getUrls(Date createdAfter, Date createdBefore) throws ContentIOException + { + Set urls = new HashSet(1139); + List stores = getAllStores(); + for (ContentStore store : stores) + { + Set storeUrls = store.getUrls(createdAfter, createdBefore); + urls.addAll(storeUrls); + } + if (logger.isDebugEnabled()) + { + logger.debug("Found " + urls.size() + " URLs from " + stores.size() + " stores"); + } + return urls; + } + + /** + * Selects a store for the given context and caches store that was used. + * + * @see #selectWriteStore(ContentContext) + */ + public ContentWriter getWriter(ContentContext context) throws ContentIOException + { + // Select the store for writing + ContentStore store = selectWriteStore(context); + ContentWriter writer = store.getWriter(context); + // Cache the store against the URL + storesCacheWriteLock.lock(); + try + { + String contentUrl = writer.getContentUrl(); + storesByContentUrl.put(contentUrl, store); + } + finally + { + storesCacheWriteLock.unlock(); + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Got writer and cache URL from store: \n" + + " Context: " + context + "\n" + + " Writer: " + writer + "\n" + + " Store: " + store); + } + return writer; + } + + /** + * @see + */ + public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException + { + return getWriter(new ContentContext(existingContentReader, newContentUrl)); + } +} diff --git a/source/java/org/alfresco/repo/content/ContentContext.java b/source/java/org/alfresco/repo/content/ContentContext.java new file mode 100644 index 0000000000..8ec6b0bacc --- /dev/null +++ b/source/java/org/alfresco/repo/content/ContentContext.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2007 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; + +import java.io.Serializable; + +import org.alfresco.service.cmr.repository.ContentReader; + +/** + * The location and lookup data for content. The very least data required to + * find content or assign a content writer is the content URL and any previous + * content that may have logically existed. + *

+ * Although this class is doesn't enforce any conditions on the context, + * derived instances may have relationships that need to be maintained between + * various context values. + * + * @author Derek Hulley + */ +public class ContentContext implements Serializable +{ + private static final long serialVersionUID = 6476617391229895125L; + + /** An empty context. */ + public static final ContentContext NULL_CONTEXT = new ContentContext(null, null); + + private ContentReader existingContentReader; + private String contentUrl; + + /** + * Construct the instance with the content URL. + * + * @param existingContentReader content with which to seed the new writer - may be null + * @param contentUrl the content URL - may be null + */ + public ContentContext(ContentReader existingContentReader, String contentUrl) + { + this.existingContentReader = existingContentReader; + this.contentUrl = contentUrl; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(128); + sb.append("ContentContext") + .append("[ contentUrl=").append(getContentUrl()) + .append(", existing=").append((getExistingContentReader() == null ? false : true)) + .append("]"); + return sb.toString(); + } + + /** + * @return Returns the content to seed the writer with - may be null + */ + public ContentReader getExistingContentReader() + { + return existingContentReader; + } + + /** + * @return Returns the content URL for the content's context - may be null + */ + public String getContentUrl() + { + return contentUrl; + } + +} diff --git a/source/java/org/alfresco/repo/content/ContentStore.java b/source/java/org/alfresco/repo/content/ContentStore.java index 1552410755..d8d379e55b 100644 --- a/source/java/org/alfresco/repo/content/ContentStore.java +++ b/source/java/org/alfresco/repo/content/ContentStore.java @@ -27,6 +27,7 @@ package org.alfresco.repo.content; import java.util.Date; import java.util.Set; +import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; @@ -48,6 +49,8 @@ import org.alfresco.service.cmr.repository.ContentWriter; *

  • year: year
  • *
  • month: 1-based month of the year
  • *
  • day: 1-based day of the month
  • + *
  • hour: 0-based hour of the day
  • + *
  • minute: 0-based minute of the hour
  • *
  • GUID: A unique identifier
  • * * The old file:// prefix must still be supported - and functionality @@ -77,14 +80,12 @@ public interface ContentStore public boolean exists(String contentUrl) throws ContentIOException; /** - * Get the accessor with which to read from the content - * at the given URL. The reader is stateful and - * can only be used once. + * Get the accessor with which to read from the content at the given URL. + * The reader is stateful and can only be used once. * * @param contentUrl the path to where the content is located * @return Returns a read-only content accessor for the given URL. There may * be no content at the given URL, but the reader must still be returned. - * The reader may implement the {@link RandomAccessContent random access interface}. * @throws ContentIOException * * @see #exists(String) @@ -98,22 +99,31 @@ public interface ContentStore * only be used once. The location may be specified but must, in that case, * be a valid and unused URL. *

    + * The store will ensure that the {@link ContentAccessor#getContentUrl() new content URL} will + * be valid for all subsequent read attempts. + *

    * By supplying a reader to existing content, the store implementation may * enable {@link RandomAccessContent random access}. The store implementation * can enable this by copying the existing content into the new location * before supplying a writer onto the new content. * - * @param existingContentReader a reader onto any existing content for which - * a writer is required - may be null - * @param newContentUrl an unused, valid URL to use - may be null. - * @return Returns a write-only content accessor, possibly implementing - * the {@link RandomAccessContent random access interface} - * @throws ContentIOException if completely new content storage could not be - * created + * @param context the context of content. + * @return Returns a write-only content accessor + * @throws ContentIOException if completely new content storage could not be created * + * @see #getWriter(ContentReader, String) * @see ContentWriter#addListener(ContentStreamListener) * @see ContentWriter#getContentUrl() */ + public ContentWriter getWriter(ContentContext context) throws ContentIOException; + + /** + * Shortcut method to {@link #getWriter(ContentContext)}. + * + * @see #getWriter(ContentContext) + * + * @deprecated + */ public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException; /** @@ -140,13 +150,11 @@ public interface ContentStore *

    * A delete cannot be forced since it is much better to have the * file remain longer than desired rather than deleted prematurely. - * The store implementation should safeguard files for certain - * minimum period, in which case all files younger than a certain - * age will not be deleted. * * @param contentUrl the URL of the content to delete * @return Return true if the content was deleted (either by this or - * another operation), otherwise false + * another operation), otherwise false. If the content no longer + * exists, then true is returned. * @throws ContentIOException */ public boolean delete(String contentUrl) throws ContentIOException; diff --git a/source/java/org/alfresco/repo/content/EmptyContentReader.java b/source/java/org/alfresco/repo/content/EmptyContentReader.java new file mode 100644 index 0000000000..fc342df616 --- /dev/null +++ b/source/java/org/alfresco/repo/content/EmptyContentReader.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2007 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; + +import java.nio.channels.ReadableByteChannel; + +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; + +/** + * A blank reader for which exists() always returns false. + * + * @author Derek Hulley + */ +public class EmptyContentReader extends AbstractContentReader +{ + /** + * @param contentUrl the content URL + */ + public EmptyContentReader(String contentUrl) + { + super(contentUrl); + } + + /** + * @return Returns an instance of the this class + */ + @Override + protected ContentReader createReader() throws ContentIOException + { + return new EmptyContentReader(this.getContentUrl()); + } + + @Override + protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException + { + throw new UnsupportedOperationException("The content never exists"); + } + + public boolean exists() + { + return false; + } + + public long getLastModified() + { + return 0L; + } + + public long getSize() + { + return 0L; + } +} diff --git a/source/java/org/alfresco/repo/content/NodeContentContext.java b/source/java/org/alfresco/repo/content/NodeContentContext.java new file mode 100644 index 0000000000..392ec56c79 --- /dev/null +++ b/source/java/org/alfresco/repo/content/NodeContentContext.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2007 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; + +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; + +/** + * Context information for node-related content. + * + * @author Derek Hulley + */ +public class NodeContentContext extends ContentContext +{ + private static final long serialVersionUID = -1836714367516857907L; + + private NodeRef nodeRef; + private QName propertyQName; + + /** + * Construct the instance with the content URL. + * + * @param existingContentReader content with which to seed the new writer - may be null + * @param contentUrl the content URL - may be null + * @param nodeRef the node holding the content metadata - may not be null + * @param propertyQName the property holding the content metadata - may not be null + */ + public NodeContentContext( + ContentReader existingContentReader, + String contentUrl, + NodeRef nodeRef, + QName propertyQName) + { + super(existingContentReader, contentUrl); + ParameterCheck.mandatory("nodeRef", nodeRef); + ParameterCheck.mandatory("propertyQName", propertyQName); + this.nodeRef = nodeRef; + this.propertyQName = propertyQName; + } + + /** + * @return Returns the node holding the content metadata + */ + public NodeRef getNodeRef() + { + return nodeRef; + } + + /** + * + * @return Returns the property holding the content metadata + */ + public QName getPropertyQName() + { + return propertyQName; + } +} diff --git a/source/java/org/alfresco/repo/content/RoutingContentService.java b/source/java/org/alfresco/repo/content/RoutingContentService.java index f0156edd66..d20ca5adad 100644 --- a/source/java/org/alfresco/repo/content/RoutingContentService.java +++ b/source/java/org/alfresco/repo/content/RoutingContentService.java @@ -77,7 +77,6 @@ public class RoutingContentService implements ContentService { private static Log logger = LogFactory.getLog(RoutingContentService.class); - private TransactionService transactionService; private DictionaryService dictionaryService; private NodeService nodeService; private AVMService avmService; @@ -110,9 +109,12 @@ public class RoutingContentService implements ContentService this.tempStore = new FileContentStore(TempFileProvider.getTempDir().getAbsolutePath()); } + /** + * @deprecated Replaced by {@link #setRetryingTransactionHelper(RetryingTransactionHelper)} + */ public void setTransactionService(TransactionService transactionService) { - this.transactionService = transactionService; + logger.warn("Property 'transactionService' has been replaced by 'retryingTransactionHelper'."); } public void setRetryingTransactionHelper(RetryingTransactionHelper helper) @@ -313,7 +315,7 @@ public class RoutingContentService implements ContentService } String contentUrl = contentData.getContentUrl(); - // TODO: Choose the store to read from at runtime + // The context of the read is entirely described by the URL ContentReader reader = store.getReader(contentUrl); // set extra data on the reader @@ -338,12 +340,11 @@ public class RoutingContentService implements ContentService public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update) { - // TODO: Choose the store to write to at runtime - if (nodeRef == null) { + ContentContext ctx = new ContentContext(null, null); // for this case, we just give back a valid URL into the content store - ContentWriter writer = store.getWriter(null, null); + ContentWriter writer = store.getWriter(ctx); // done return writer; } @@ -353,7 +354,8 @@ public class RoutingContentService implements ContentService // get the content using the (potentially) existing content - the new content // can be wherever the store decides. - ContentWriter writer = store.getWriter(existingContentReader, null); + ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, propertyQName); + ContentWriter writer = store.getWriter(ctx); // Special case for AVM repository. Serializable contentValue = null; @@ -395,7 +397,7 @@ public class RoutingContentService implements ContentService public ContentWriter getTempWriter() { // there is no existing content and we don't specify the location of the new content - return tempStore.getWriter(null, null); + return tempStore.getWriter(ContentContext.NULL_CONTEXT); } /** diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java index 7d21aa7b4d..3c8d0ba09f 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java +++ b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java @@ -32,6 +32,7 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentContext; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; @@ -39,10 +40,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Provides a store of node content directly to the file system. + * Provides a store of node content directly to the file system. The writers + * are generated using information from the {@link ContentContext simple content context}. *

    * The file names obey, as they must, the URL naming convention - * as specified in the {@link org.alfresco.repo.content.ContentStore}. + * as specified in the {@link org.alfresco.repo.content.ContentStore ContentStore interface}. * * @author Derek Hulley */ @@ -241,8 +243,10 @@ public class FileContentStore extends AbstractContentStore /** * @return Returns a writer onto a location based on the date */ - public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) + public ContentWriter getWriter(ContentContext ctx) { + ContentReader existingContentReader = ctx.getExistingContentReader(); + String newContentUrl = ctx.getContentUrl(); try { File file = null; diff --git a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java index b77aa1ae7e..6d1f7e0156 100644 --- a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java +++ b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java @@ -35,6 +35,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentContext; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.repository.ContentIOException; @@ -106,7 +107,6 @@ public class ReplicatingContentStore extends AbstractContentStore private static Log logger = LogFactory.getLog(ReplicatingContentStore.class); - private TransactionService transactionService; private RetryingTransactionHelper transactionHelper; private ContentStore primaryStore; private List secondaryStores; @@ -131,17 +131,17 @@ public class ReplicatingContentStore extends AbstractContentStore } /** - * Required to ensure that content listeners are executed in a transaction - * - * @param transactionService + * @deprecated Replaced with {@link #setRetryingTransactionHelper(RetryingTransactionHelper)} */ public void setTransactionService(TransactionService transactionService) { - this.transactionService = transactionService; + logger.warn("Property 'transactionService' has been replaced with 'retryingTransactionHelper'."); } /** * Set the retrying transaction helper. + * + * @since 2.0 */ public void setRetryingTransactionHelper(RetryingTransactionHelper helper) { @@ -273,7 +273,8 @@ public class ReplicatingContentStore extends AbstractContentStore return primaryContentReader; } // get a writer - ContentWriter primaryContentWriter = primaryStore.getWriter(existingContentReader, contentUrl); + ContentContext ctx = new ContentContext(existingContentReader, contentUrl); + ContentWriter primaryContentWriter = primaryStore.getWriter(ctx); // copy it over primaryContentWriter.putContent(existingContentReader); // get a writer to the new content @@ -287,13 +288,10 @@ public class ReplicatingContentStore extends AbstractContentStore } } - /** - * - */ - public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException + public ContentWriter getWriter(ContentContext ctx) { // get the writer - ContentWriter writer = primaryStore.getWriter(existingContentReader, newContentUrl); + ContentWriter writer = primaryStore.getWriter(ctx); // attach a replicating listener if outbound replication is on if (outbound) @@ -434,7 +432,8 @@ public class ReplicatingContentStore extends AbstractContentStore ContentReader reader = writer.getReader(); String contentUrl = reader.getContentUrl(); // in order to replicate, we have to specify the URL that we are going to write to - ContentWriter replicatedWriter = store.getWriter(null, contentUrl); + ContentContext ctx = new ContentContext(null, contentUrl); + ContentWriter replicatedWriter = store.getWriter(ctx); // write it replicatedWriter.putContent(reader); diff --git a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java index 97b83be782..db9bc1ba49 100644 --- a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java +++ b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java @@ -33,9 +33,9 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.alfresco.repo.content.AbstractContentReadWriteTest; +import org.alfresco.repo.content.ContentContext; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.filestore.FileContentStore; -import org.alfresco.repo.transaction.DummyTransactionService; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.util.GUID; @@ -77,7 +77,6 @@ public class ReplicatingContentStoreTest extends AbstractContentReadWriteTest secondaryStores.add(store); } replicatingStore = new ReplicatingContentStore(); - replicatingStore.setTransactionService(new DummyTransactionService()); replicatingStore.setPrimaryStore(primaryStore); replicatingStore.setSecondaryStores(secondaryStores); replicatingStore.setOutbound(false); @@ -184,7 +183,7 @@ public class ReplicatingContentStoreTest extends AbstractContentReadWriteTest // pick a secondary store and write some content to it ContentStore secondaryStore = secondaryStores.get(2); - ContentWriter writer = secondaryStore.getWriter(null, null); + ContentWriter writer = secondaryStore.getWriter(ContentContext.NULL_CONTEXT); writer.putContent(SOME_CONTENT); String contentUrl = writer.getContentUrl(); diff --git a/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java b/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java index bde362534d..0f31872497 100644 --- a/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java +++ b/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java @@ -28,6 +28,7 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentContext; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.search.Indexer; @@ -148,7 +149,8 @@ public class MissingContentReindexComponentTest extends TestCase } // now put some content in the store - ContentWriter writer = contentStore.getWriter(null, contentUrl); + ContentContext ctx = new ContentContext(null, contentUrl); + ContentWriter writer = contentStore.getWriter(ctx); writer.setMimetype("text/plain"); writer.setEncoding("UTF8"); writer.putContent("123abc456def");