mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-01 14:41:46 +00:00
ACS-1631 : Storage classes - REST api - POST & GET /nodes/{nodeId}/children (#540)
This commit is contained in:
committed by
Andrea Ligios
parent
cf14112626
commit
870a9ee4fd
@@ -64,6 +64,19 @@ public class ContentContext implements Serializable
|
||||
this.contentUrl = contentUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the instance with the content URL.
|
||||
*
|
||||
* @param existingContentReader content with which to seed the new writer - may be <tt>null</tt>
|
||||
* @param contentUrl the content URL - may be <tt>null</tt>
|
||||
* @param storageClasses the storage classes specific to the provided content URL - may be <tt>null</tt>
|
||||
*/
|
||||
public ContentContext(ContentReader existingContentReader, String contentUrl, Set<String> storageClasses)
|
||||
{
|
||||
this(existingContentReader, contentUrl);
|
||||
this.storageClasses = storageClasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@@ -37,6 +37,11 @@ import java.util.HashSet;
|
||||
*/
|
||||
public class StorageClassSet extends HashSet<String>
|
||||
{
|
||||
public StorageClassSet()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public StorageClassSet(String... storageClasses)
|
||||
{
|
||||
super();
|
||||
|
@@ -56,6 +56,8 @@ import org.alfresco.repo.action.executer.ContentMetadataExtracter;
|
||||
import org.alfresco.repo.activities.ActivityType;
|
||||
import org.alfresco.repo.content.ContentLimitViolationException;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.repo.content.StorageClassSet;
|
||||
import org.alfresco.repo.content.UnsupportedStorageClassException;
|
||||
import org.alfresco.repo.domain.node.AuditablePropertiesEntity;
|
||||
import org.alfresco.repo.lock.mem.Lifetime;
|
||||
import org.alfresco.repo.model.Repository;
|
||||
@@ -105,6 +107,7 @@ import org.alfresco.rest.framework.core.exceptions.RequestEntityTooLargeExceptio
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedMediaTypeException;
|
||||
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
|
||||
import org.alfresco.rest.framework.resource.content.BinaryResource;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfo;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfoImpl;
|
||||
import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
@@ -1048,8 +1051,8 @@ public class NodesImpl implements Nodes
|
||||
node.setNodeType(nodeTypeQName.toPrefixString(namespaceService));
|
||||
node.setPath(pathInfo);
|
||||
|
||||
|
||||
if (includeParam.contains(PARAM_INCLUDE_STORAGECLASSES))
|
||||
if (includeParam.contains(PARAM_INCLUDE_STORAGECLASSES) && node.getIsFile()
|
||||
&& node.getContent().getSizeInBytes() > 0)
|
||||
{
|
||||
node.getContent().setStorageClasses(contentService.findStorageClasses(nodeRef));
|
||||
}
|
||||
@@ -1883,7 +1886,8 @@ public class NodesImpl implements Nodes
|
||||
if (isContent)
|
||||
{
|
||||
// create empty file node - note: currently will be set to default encoding only (UTF-8)
|
||||
nodeRef = createNewFile(parentNodeRef, nodeName, nodeTypeQName, null, props, assocTypeQName, parameters, versionMajor, versionComment);
|
||||
nodeRef = createNewFile(parentNodeRef, nodeName, nodeTypeQName, null, null, props,
|
||||
assocTypeQName, parameters, versionMajor, versionComment);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2778,7 +2782,13 @@ public class NodesImpl implements Nodes
|
||||
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
try
|
||||
{
|
||||
writeContent(nodeRef, fileName, stream, true);
|
||||
writeContent(nodeRef,
|
||||
fileName,
|
||||
stream,
|
||||
true,
|
||||
contentInfo instanceof ContentInfo ?
|
||||
((ContentInfo) contentInfo).getStorageClasses() :
|
||||
null);
|
||||
|
||||
if ((isVersioned) || (versionMajor != null) || (versionComment != null) )
|
||||
{
|
||||
@@ -2817,10 +2827,17 @@ public class NodesImpl implements Nodes
|
||||
}
|
||||
|
||||
private void writeContent(NodeRef nodeRef, String fileName, InputStream stream, boolean guessEncoding)
|
||||
{
|
||||
writeContent(nodeRef, fileName, stream, guessEncoding, null);
|
||||
}
|
||||
|
||||
private void writeContent(NodeRef nodeRef, String fileName, InputStream stream,
|
||||
boolean guessEncoding, StorageClassSet storageClassSet)
|
||||
{
|
||||
try
|
||||
{
|
||||
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
|
||||
ContentWriter writer = contentService
|
||||
.getWriter(nodeRef, ContentModel.PROP_CONTENT, true, storageClassSet);
|
||||
|
||||
String mimeType = mimetypeService.guessMimetype(fileName);
|
||||
if ((mimeType != null) && (!mimeType.equals(MimetypeMap.MIMETYPE_BINARY)))
|
||||
@@ -2947,6 +2964,7 @@ public class NodesImpl implements Nodes
|
||||
String relativePath = null;
|
||||
String renditionNames = null;
|
||||
boolean versioningEnabled = true;
|
||||
String storageClassesParam = null;
|
||||
|
||||
Map<String, Object> qnameStrProps = new HashMap<>();
|
||||
Map<QName, Serializable> properties = null;
|
||||
@@ -2984,6 +3002,10 @@ public class NodesImpl implements Nodes
|
||||
}
|
||||
break;
|
||||
|
||||
case "storageclasses":
|
||||
storageClassesParam = getStringOrNull(field.getValue());
|
||||
break;
|
||||
|
||||
case "overwrite":
|
||||
overwrite = Boolean.valueOf(field.getValue());
|
||||
break;
|
||||
@@ -3052,8 +3074,9 @@ public class NodesImpl implements Nodes
|
||||
parentNodeRef = getOrCreatePath(parentNodeRef, relativePath);
|
||||
final QName assocTypeQName = ContentModel.ASSOC_CONTAINS;
|
||||
final Set<String> renditions = getRequestedRenditions(renditionNames);
|
||||
final StorageClassSet storageClasses = getRequestedStorageClasses(storageClassesParam);
|
||||
|
||||
validateProperties(qnameStrProps, EXCLUDED_NS, Arrays.asList());
|
||||
validateProperties(qnameStrProps, EXCLUDED_NS, Collections.emptyList());
|
||||
try
|
||||
{
|
||||
// Map the given properties, if any.
|
||||
@@ -3079,8 +3102,13 @@ public class NodesImpl implements Nodes
|
||||
else if (overwrite && nodeService.hasAspect(existingFile, ContentModel.ASPECT_VERSIONABLE))
|
||||
{
|
||||
// overwrite existing (versionable) file
|
||||
BasicContentInfo contentInfo = new ContentInfoImpl(content.getMimetype(), content.getEncoding(), -1, null);
|
||||
return updateExistingFile(parentNodeRef, existingFile, fileName, contentInfo, content.getInputStream(), parameters, versionMajor, versionComment);
|
||||
|
||||
BasicContentInfo contentInfo = new ContentInfoImpl(content.getMimetype(),
|
||||
content.getEncoding(), -1,
|
||||
null, storageClasses);
|
||||
return updateExistingFile(parentNodeRef, existingFile, fileName, contentInfo,
|
||||
content.getInputStream(), parameters, versionMajor,
|
||||
versionComment);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3098,7 +3126,9 @@ public class NodesImpl implements Nodes
|
||||
versionMajor = versioningEnabled ? versionMajor : null;
|
||||
|
||||
// Create a new file.
|
||||
NodeRef nodeRef = createNewFile(parentNodeRef, fileName, nodeTypeQName, content, properties, assocTypeQName, parameters, versionMajor, versionComment);
|
||||
NodeRef nodeRef = createNewFile(parentNodeRef, fileName, nodeTypeQName, content,
|
||||
storageClasses, properties, assocTypeQName, parameters,
|
||||
versionMajor, versionComment);
|
||||
|
||||
// Create the response
|
||||
final Node fileNode = getFolderOrDocumentFullInfo(nodeRef, parentNodeRef, nodeTypeQName, parameters);
|
||||
@@ -3115,6 +3145,10 @@ public class NodesImpl implements Nodes
|
||||
{
|
||||
throw new PermissionDeniedException(ade.getMessage());
|
||||
}
|
||||
catch (UnsupportedStorageClassException usce)
|
||||
{
|
||||
throw new InvalidArgumentException(usce.getMessage());
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Do not clean formData temp files to allow for retries. It's
|
||||
@@ -3123,8 +3157,9 @@ public class NodesImpl implements Nodes
|
||||
*/
|
||||
}
|
||||
|
||||
private NodeRef createNewFile(NodeRef parentNodeRef, String fileName, QName nodeType, Content content, Map<QName, Serializable> props, QName assocTypeQName, Parameters params,
|
||||
Boolean versionMajor, String versionComment)
|
||||
private NodeRef createNewFile(NodeRef parentNodeRef, String fileName, QName nodeType,
|
||||
Content content, StorageClassSet storageClassSet, Map<QName, Serializable> props,
|
||||
QName assocTypeQName, Parameters params, Boolean versionMajor, String versionComment)
|
||||
{
|
||||
NodeRef nodeRef = createNodeImpl(parentNodeRef, fileName, nodeType, props, assocTypeQName);
|
||||
|
||||
@@ -3136,7 +3171,7 @@ public class NodesImpl implements Nodes
|
||||
else
|
||||
{
|
||||
// Write content
|
||||
writeContent(nodeRef, fileName, content.getInputStream(), true);
|
||||
writeContent(nodeRef, fileName, content.getInputStream(), true, storageClassSet);
|
||||
}
|
||||
|
||||
if ((versionMajor != null) || (versionComment != null))
|
||||
@@ -3214,6 +3249,21 @@ public class NodesImpl implements Nodes
|
||||
return renditions;
|
||||
}
|
||||
|
||||
static StorageClassSet getRequestedStorageClasses(String storageClassesParam)
|
||||
{
|
||||
if (storageClassesParam == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] storageClasses = Arrays.stream(storageClassesParam.split(","))
|
||||
.map(String::trim)
|
||||
.filter(sc -> !sc.isEmpty())
|
||||
.toArray(String[]::new);
|
||||
|
||||
return new StorageClassSet(storageClasses);
|
||||
}
|
||||
|
||||
private void requestRenditions(Set<String> renditionNames, Node fileNode)
|
||||
{
|
||||
if (renditionNames != null)
|
||||
|
@@ -27,6 +27,8 @@ package org.alfresco.rest.api.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.content.StorageClassSet;
|
||||
|
||||
/**
|
||||
* Representation of content info
|
||||
*
|
||||
@@ -39,7 +41,7 @@ public class ContentInfo
|
||||
private String mimeTypeName;
|
||||
private Long sizeInBytes;
|
||||
private String encoding;
|
||||
private Set<String> storageClasses;
|
||||
private StorageClassSet storageClassSet;
|
||||
|
||||
public ContentInfo()
|
||||
{
|
||||
@@ -53,13 +55,13 @@ public class ContentInfo
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public ContentInfo(String mimeType, String mimeTypeName, Long sizeInBytes, String encoding, Set<String> storageClasses)
|
||||
public ContentInfo(String mimeType, String mimeTypeName, Long sizeInBytes, String encoding, StorageClassSet storageClassSet)
|
||||
{
|
||||
this.mimeType = mimeType;
|
||||
this.mimeTypeName = mimeTypeName;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.encoding = encoding;
|
||||
this.storageClasses = storageClasses;
|
||||
this.storageClassSet = storageClassSet;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
@@ -82,20 +84,21 @@ public class ContentInfo
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public Set<String> getStorageClasses()
|
||||
public StorageClassSet getStorageClasses()
|
||||
{
|
||||
return storageClasses;
|
||||
return storageClassSet;
|
||||
}
|
||||
|
||||
public void setStorageClasses(Set<String> storageClasses)
|
||||
public void setStorageClasses(StorageClassSet storageClassSet)
|
||||
{
|
||||
this.storageClasses = storageClasses;
|
||||
this.storageClassSet = storageClassSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ContentInfo [mimeType=" + mimeType + ", mimeTypeName=" + mimeTypeName
|
||||
+ ", encoding=" + encoding + ", sizeInBytes=" + sizeInBytes + ", storageClasses=" + storageClasses + "]";
|
||||
+ ", encoding=" + encoding + ", sizeInBytes=" + sizeInBytes + ", storageClasses=" + storageClassSet
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
@@ -28,11 +28,13 @@ package org.alfresco.rest.framework.resource.content;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.content.StorageClassSet;
|
||||
|
||||
/**
|
||||
* Basic information about content. Typically used with HTTPServletResponse
|
||||
*/
|
||||
public interface ContentInfo extends BasicContentInfo{
|
||||
public long getLength();
|
||||
public Locale getLocale();
|
||||
public Set<String> getStorageClasses();
|
||||
public StorageClassSet getStorageClasses();
|
||||
}
|
||||
|
@@ -26,7 +26,8 @@
|
||||
package org.alfresco.rest.framework.resource.content;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.content.StorageClassSet;
|
||||
|
||||
/**
|
||||
* Basic implementation of information about the returned content.
|
||||
@@ -37,21 +38,21 @@ public class ContentInfoImpl implements ContentInfo
|
||||
private final String encoding;
|
||||
private final long length;
|
||||
private final Locale locale;
|
||||
private final Set<String> storageClasses;
|
||||
private final StorageClassSet storageClassSet;
|
||||
|
||||
public ContentInfoImpl(String mimeType, String encoding, long length, Locale locale)
|
||||
{
|
||||
this(mimeType, encoding, length, locale, null);
|
||||
}
|
||||
|
||||
public ContentInfoImpl(String mimeType, String encoding, long length, Locale locale, Set<String> storageClasses)
|
||||
public ContentInfoImpl(String mimeType, String encoding, long length, Locale locale, StorageClassSet storageClassSet)
|
||||
{
|
||||
super();
|
||||
this.mimeType = mimeType;
|
||||
this.encoding = encoding;
|
||||
this.length = length;
|
||||
this.locale = locale;
|
||||
this.storageClasses = storageClasses;
|
||||
this.storageClassSet = storageClassSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,8 +77,8 @@ public class ContentInfoImpl implements ContentInfo
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getStorageClasses()
|
||||
public StorageClassSet getStorageClasses()
|
||||
{
|
||||
return this.storageClasses;
|
||||
return this.storageClassSet;
|
||||
}
|
||||
}
|
||||
|
@@ -2800,6 +2800,80 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
|
||||
post(postUrl, toJsonAsStringNonNull(d1), "?"+Nodes.PARAM_AUTO_RENAME+"=false", 409);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadFileWithStorageClasses() throws Exception
|
||||
{
|
||||
setRequestContext(networkOne.getId(), user1, null);
|
||||
|
||||
String title = "test title";
|
||||
Map<String,String> docProps = new HashMap<>();
|
||||
docProps.put("cm:title", title);
|
||||
docProps.put("cm:owner", user1);
|
||||
docProps.put("storageClasses", "unsupported-storage-classes");
|
||||
docProps.put("include", "storageClasses");
|
||||
String contentName = "content " + RUNID + ".txt";
|
||||
|
||||
// Upload text with unsupported storage classes
|
||||
createTextFile(Nodes.PATH_MY, contentName, "The quick brown fox jumps over the lazy dog.", "UTF-8", docProps, 400);
|
||||
|
||||
// Upload text content with "default" storage classes
|
||||
docProps.put("storageClasses", "default");
|
||||
Document document = createTextFile(Nodes.PATH_MY, contentName, "The quick brown fox jumps over the lazy dog.", "UTF-8", docProps);
|
||||
|
||||
assertTrue(Set.of("default").containsAll(document.getContent().getStorageClasses()));
|
||||
|
||||
// Upload new version with "default" storage classes
|
||||
docProps.put("overwrite", "true");
|
||||
createTextFile(Nodes.PATH_MY, contentName, "New content - The quick brown fox jumps over the lazy dog.", "UTF-8", docProps);
|
||||
|
||||
HttpResponse response = getAll(getNodeChildrenUrl(Nodes.PATH_MY), getPaging(0, 100), Map.of("include", "storageClasses"), 200);
|
||||
List<Node> children = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
|
||||
|
||||
assertEquals(1, children.size());
|
||||
assertTrue(Set.of("default").containsAll(children.get(0).getContent().getStorageClasses()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetChildrenWithNoStorageClasses() throws Exception
|
||||
{
|
||||
setRequestContext(networkOne.getId(), user1, null);
|
||||
|
||||
// Create folder
|
||||
createFolder(Nodes.PATH_MY, "testFolder");
|
||||
|
||||
Map params = new HashMap<>();
|
||||
params.put("storageClasses", "default");
|
||||
params.put("include", "storageClasses");
|
||||
|
||||
// Create empty file
|
||||
Document emptyTextFile = createEmptyTextFile(Nodes.PATH_MY, "empty-file.txt", params, 201);
|
||||
|
||||
assertNotNull(emptyTextFile.getContent());
|
||||
assertNull(
|
||||
emptyTextFile.getContent().getStorageClasses()); // no storage classes for empty files
|
||||
|
||||
// Create file with content - default storage classes
|
||||
Document fileWithContent = createTextFile(Nodes.PATH_MY, "file-with-content.txt",
|
||||
"The quick brown fox jumps over the lazy dog.",
|
||||
"UTF-8", params);
|
||||
|
||||
assertNotNull(fileWithContent.getContent());
|
||||
assertTrue(Set.of("default").containsAll(fileWithContent.getContent().getStorageClasses()));
|
||||
|
||||
HttpResponse response = getAll(getNodeChildrenUrl(Nodes.PATH_MY), getPaging(0, 100),
|
||||
Map.of("include", "storageClasses"), 200);
|
||||
List<Node> children = RestApiUtil
|
||||
.parseRestApiEntries(response.getJsonResponse(), Node.class);
|
||||
|
||||
assertEquals(3, children.size());
|
||||
long childrenWithStorageClasses = children
|
||||
.stream()
|
||||
.filter(child -> child.getContent() != null &&
|
||||
child.getContent().getStorageClasses() != null)
|
||||
.count();
|
||||
assertEquals(1, childrenWithStorageClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateNodeConcurrentlyUsingInMemoryBacked() throws Exception
|
||||
{
|
||||
@@ -4784,16 +4858,15 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
|
||||
{
|
||||
setRequestContext(user1);
|
||||
|
||||
// Create folder with an empty document
|
||||
String postUrl = createFolder();
|
||||
String docId = createDocument(postUrl);
|
||||
Document document = createTextFile(Nodes.PATH_MY, "file.txt",
|
||||
"The quick brown fox jumps over the lazy dog.");
|
||||
|
||||
Map params = new HashMap<>();
|
||||
params.put("include", "storageClasses");
|
||||
|
||||
// Update node
|
||||
Document dUpdate = new Document();
|
||||
HttpResponse response = put(URL_NODES, docId, toJsonAsStringNonNull(dUpdate), null, 200);
|
||||
HttpResponse response = put(URL_NODES, document.getId(), toJsonAsStringNonNull(dUpdate), null, 200);
|
||||
Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
|
||||
|
||||
// Check if storageClasses are retrieved if 'include=storageClasses' is not sent in the request
|
||||
|
@@ -30,6 +30,8 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.content.StorageClassSet;
|
||||
|
||||
/**
|
||||
* Representation of content info (initially for client tests for File Folder API)
|
||||
*
|
||||
@@ -42,7 +44,7 @@ public class ContentInfo
|
||||
private String mimeTypeName;
|
||||
private Long sizeInBytes;
|
||||
private String encoding;
|
||||
private Set<String> storageClasses;
|
||||
private StorageClassSet storageClassSet;
|
||||
|
||||
public ContentInfo()
|
||||
{
|
||||
@@ -80,14 +82,14 @@ public class ContentInfo
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public Set<String> getStorageClasses()
|
||||
public StorageClassSet getStorageClasses()
|
||||
{
|
||||
return storageClasses;
|
||||
return storageClassSet;
|
||||
}
|
||||
|
||||
public void setStorageClasses(Set<String> storageClasses)
|
||||
public void setStorageClasses(StorageClassSet storageClassSet)
|
||||
{
|
||||
this.storageClasses = storageClasses;
|
||||
this.storageClassSet = storageClassSet;
|
||||
}
|
||||
|
||||
public void expected(Object o)
|
||||
@@ -100,7 +102,7 @@ public class ContentInfo
|
||||
AssertUtil.assertEquals("mimeTypeName", mimeTypeName, other.getMimeTypeName());
|
||||
AssertUtil.assertEquals("sizeInBytes", sizeInBytes, other.getSizeInBytes());
|
||||
AssertUtil.assertEquals("encoding", encoding, other.getEncoding());
|
||||
AssertUtil.assertEquals("storageClasses", storageClasses, other.storageClasses);
|
||||
AssertUtil.assertEquals("storageClasses", storageClassSet, other.storageClassSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,7 +113,7 @@ public class ContentInfo
|
||||
.append(", mimeTypeName=").append(mimeTypeName)
|
||||
.append(", sizeInBytes=").append(sizeInBytes)
|
||||
.append(", encoding=").append(encoding)
|
||||
.append(", storageClasses=").append(storageClasses)
|
||||
.append(", storageClasses=").append(storageClassSet)
|
||||
.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
@@ -449,9 +449,21 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa
|
||||
|
||||
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update)
|
||||
{
|
||||
return getWriter(nodeRef,propertyQName, update, null);
|
||||
}
|
||||
|
||||
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update,
|
||||
StorageClassSet storageClassSet)
|
||||
{
|
||||
if (!isStorageClassesSupported(storageClassSet))
|
||||
{
|
||||
throw new UnsupportedStorageClassException(store, storageClassSet,
|
||||
"The supplied storage classes are not supported");
|
||||
}
|
||||
|
||||
if (nodeRef == null)
|
||||
{
|
||||
ContentContext ctx = new ContentContext(null, null);
|
||||
ContentContext ctx = new ContentContext(null, null, storageClassSet);
|
||||
// for this case, we just give back a valid URL into the content store
|
||||
ContentWriter writer = store.getWriter(ctx);
|
||||
// Register the new URL for rollback cleanup
|
||||
@@ -463,9 +475,37 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa
|
||||
// check for an existing URL - the get of the reader will perform type checking
|
||||
ContentReader existingContentReader = getReader(nodeRef, propertyQName, false);
|
||||
|
||||
if (storageClassSet != null)
|
||||
{
|
||||
if (existingContentReader != null &&
|
||||
existingContentReader.getContentData() != null &&
|
||||
existingContentReader.getContentData().getContentUrl() != null)
|
||||
{
|
||||
Set<String> currentStorageClasses = findStorageClasses(nodeRef);
|
||||
if (currentStorageClasses != null &&
|
||||
!currentStorageClasses.equals(storageClassSet))
|
||||
{
|
||||
Set<StorageClassSet> possibleTransitions = findStorageClassesTransitions(nodeRef)
|
||||
.get(currentStorageClasses);
|
||||
|
||||
if (possibleTransitions == null ||
|
||||
!possibleTransitions.contains(storageClassSet))
|
||||
{
|
||||
throw new UnsupportedStorageClassException(store, storageClassSet,
|
||||
"Transition from "
|
||||
+ currentStorageClasses
|
||||
+ " storage classes to "
|
||||
+ storageClassSet
|
||||
+ " is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the content using the (potentially) existing content - the new content
|
||||
// can be wherever the store decides.
|
||||
ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, propertyQName);
|
||||
ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef,
|
||||
propertyQName, storageClassSet);
|
||||
ContentWriter writer = store.getWriter(ctx);
|
||||
// Register the new URL for rollback cleanup
|
||||
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
||||
|
@@ -25,6 +25,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.content;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
@@ -63,6 +65,29 @@ public class NodeContentContext extends ContentContext
|
||||
this.propertyQName = propertyQName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the instance with the content URL.
|
||||
*
|
||||
* @param existingContentReader content with which to seed the new writer - may be <tt>null</tt>
|
||||
* @param contentUrl the content URL - may be <tt>null</tt>
|
||||
* @param nodeRef the node holding the content metadata - may not be <tt>null</tt>
|
||||
* @param propertyQName the property holding the content metadata - may not be <tt>null</tt>
|
||||
* @param storageClasses the storage classes specific to the provided content URL - may be <tt>null</tt>
|
||||
*/
|
||||
public NodeContentContext(
|
||||
ContentReader existingContentReader,
|
||||
String contentUrl,
|
||||
NodeRef nodeRef,
|
||||
QName propertyQName,
|
||||
Set<String> storageClasses)
|
||||
{
|
||||
super(existingContentReader, contentUrl, storageClasses);
|
||||
ParameterCheck.mandatory("nodeRef", nodeRef);
|
||||
ParameterCheck.mandatory("propertyQName", propertyQName);
|
||||
this.nodeRef = nodeRef;
|
||||
this.propertyQName = propertyQName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@@ -151,6 +151,40 @@ public interface ContentService
|
||||
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update)
|
||||
throws InvalidNodeRefException, InvalidTypeException;
|
||||
|
||||
/**
|
||||
* Get a content writer for the given node property, choosing to optionally have
|
||||
* the node property updated automatically when the content stream closes.
|
||||
* <p>
|
||||
* If the update flag is off, then the state of the node property will remain unchanged
|
||||
* regardless of the state of the written binary data. If the flag is on, then the node
|
||||
* property will be updated on the same thread as the code that closed the write
|
||||
* channel.
|
||||
* <p>
|
||||
* If no node is supplied, then the writer will provide a stream into the backing content
|
||||
* store, but will not be associated with any new or previous content.
|
||||
* <p/>
|
||||
* <b>NOTE: </b>The content URL provided will be registered for automatic cleanup in the event
|
||||
* that the transaction, in which this method was called, rolls back. If the transaction
|
||||
* is successful, the writer may still be open and available for use but the underlying binary
|
||||
* will not be cleaned up subsequently. The recommended pattern is to group calls to retrieve
|
||||
* the writer in the same transaction as the calls to subsequently update and close the
|
||||
* write stream - including setting of the related content properties.
|
||||
*
|
||||
* @param nodeRef a reference to a node having a content property, or <tt>null</tt>
|
||||
* to just get a valid writer into a backing content store.
|
||||
* @param propertyQName the name of the property, which must be of type <b>content</b>
|
||||
* @param update true if the property must be updated atomically when the content write
|
||||
* stream is closed (attaches a listener to the stream); false if the client code
|
||||
* will perform the updates itself.
|
||||
* @param storageClassSet storage classes for the content associated with the node property
|
||||
* @return Returns a writer for the content associated with the node property
|
||||
* @throws InvalidNodeRefException if the node doesn't exist
|
||||
* @throws InvalidTypeException if the node property is not of type <b>content</b>
|
||||
*/
|
||||
@Auditable(parameters = {"nodeRef", "propertyQName", "update", "storageClasses"})
|
||||
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update,
|
||||
StorageClassSet storageClassSet) throws InvalidNodeRefException, InvalidTypeException;
|
||||
|
||||
/**
|
||||
* Gets a writer to a temporary location. The longevity of the stored
|
||||
* temporary content is determined by the system.
|
||||
|
Reference in New Issue
Block a user