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
@@ -63,7 +63,20 @@ public class ContentContext implements Serializable
|
|||||||
this.existingContentReader = existingContentReader;
|
this.existingContentReader = existingContentReader;
|
||||||
this.contentUrl = contentUrl;
|
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
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
@@ -37,6 +37,11 @@ import java.util.HashSet;
|
|||||||
*/
|
*/
|
||||||
public class StorageClassSet extends HashSet<String>
|
public class StorageClassSet extends HashSet<String>
|
||||||
{
|
{
|
||||||
|
public StorageClassSet()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public StorageClassSet(String... storageClasses)
|
public StorageClassSet(String... storageClasses)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
@@ -56,6 +56,8 @@ import org.alfresco.repo.action.executer.ContentMetadataExtracter;
|
|||||||
import org.alfresco.repo.activities.ActivityType;
|
import org.alfresco.repo.activities.ActivityType;
|
||||||
import org.alfresco.repo.content.ContentLimitViolationException;
|
import org.alfresco.repo.content.ContentLimitViolationException;
|
||||||
import org.alfresco.repo.content.MimetypeMap;
|
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.domain.node.AuditablePropertiesEntity;
|
||||||
import org.alfresco.repo.lock.mem.Lifetime;
|
import org.alfresco.repo.lock.mem.Lifetime;
|
||||||
import org.alfresco.repo.model.Repository;
|
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.core.exceptions.UnsupportedMediaTypeException;
|
||||||
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
|
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
|
||||||
import org.alfresco.rest.framework.resource.content.BinaryResource;
|
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.ContentInfoImpl;
|
||||||
import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
|
import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
|
||||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||||
@@ -1048,8 +1051,8 @@ public class NodesImpl implements Nodes
|
|||||||
node.setNodeType(nodeTypeQName.toPrefixString(namespaceService));
|
node.setNodeType(nodeTypeQName.toPrefixString(namespaceService));
|
||||||
node.setPath(pathInfo);
|
node.setPath(pathInfo);
|
||||||
|
|
||||||
|
if (includeParam.contains(PARAM_INCLUDE_STORAGECLASSES) && node.getIsFile()
|
||||||
if (includeParam.contains(PARAM_INCLUDE_STORAGECLASSES))
|
&& node.getContent().getSizeInBytes() > 0)
|
||||||
{
|
{
|
||||||
node.getContent().setStorageClasses(contentService.findStorageClasses(nodeRef));
|
node.getContent().setStorageClasses(contentService.findStorageClasses(nodeRef));
|
||||||
}
|
}
|
||||||
@@ -1883,7 +1886,8 @@ public class NodesImpl implements Nodes
|
|||||||
if (isContent)
|
if (isContent)
|
||||||
{
|
{
|
||||||
// create empty file node - note: currently will be set to default encoding only (UTF-8)
|
// 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
|
else
|
||||||
{
|
{
|
||||||
@@ -2778,7 +2782,13 @@ public class NodesImpl implements Nodes
|
|||||||
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
writeContent(nodeRef, fileName, stream, true);
|
writeContent(nodeRef,
|
||||||
|
fileName,
|
||||||
|
stream,
|
||||||
|
true,
|
||||||
|
contentInfo instanceof ContentInfo ?
|
||||||
|
((ContentInfo) contentInfo).getStorageClasses() :
|
||||||
|
null);
|
||||||
|
|
||||||
if ((isVersioned) || (versionMajor != null) || (versionComment != 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)
|
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
|
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);
|
String mimeType = mimetypeService.guessMimetype(fileName);
|
||||||
if ((mimeType != null) && (!mimeType.equals(MimetypeMap.MIMETYPE_BINARY)))
|
if ((mimeType != null) && (!mimeType.equals(MimetypeMap.MIMETYPE_BINARY)))
|
||||||
@@ -2947,6 +2964,7 @@ public class NodesImpl implements Nodes
|
|||||||
String relativePath = null;
|
String relativePath = null;
|
||||||
String renditionNames = null;
|
String renditionNames = null;
|
||||||
boolean versioningEnabled = true;
|
boolean versioningEnabled = true;
|
||||||
|
String storageClassesParam = null;
|
||||||
|
|
||||||
Map<String, Object> qnameStrProps = new HashMap<>();
|
Map<String, Object> qnameStrProps = new HashMap<>();
|
||||||
Map<QName, Serializable> properties = null;
|
Map<QName, Serializable> properties = null;
|
||||||
@@ -2984,6 +3002,10 @@ public class NodesImpl implements Nodes
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "storageclasses":
|
||||||
|
storageClassesParam = getStringOrNull(field.getValue());
|
||||||
|
break;
|
||||||
|
|
||||||
case "overwrite":
|
case "overwrite":
|
||||||
overwrite = Boolean.valueOf(field.getValue());
|
overwrite = Boolean.valueOf(field.getValue());
|
||||||
break;
|
break;
|
||||||
@@ -3052,8 +3074,9 @@ public class NodesImpl implements Nodes
|
|||||||
parentNodeRef = getOrCreatePath(parentNodeRef, relativePath);
|
parentNodeRef = getOrCreatePath(parentNodeRef, relativePath);
|
||||||
final QName assocTypeQName = ContentModel.ASSOC_CONTAINS;
|
final QName assocTypeQName = ContentModel.ASSOC_CONTAINS;
|
||||||
final Set<String> renditions = getRequestedRenditions(renditionNames);
|
final Set<String> renditions = getRequestedRenditions(renditionNames);
|
||||||
|
final StorageClassSet storageClasses = getRequestedStorageClasses(storageClassesParam);
|
||||||
|
|
||||||
validateProperties(qnameStrProps, EXCLUDED_NS, Arrays.asList());
|
validateProperties(qnameStrProps, EXCLUDED_NS, Collections.emptyList());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Map the given properties, if any.
|
// Map the given properties, if any.
|
||||||
@@ -3079,8 +3102,13 @@ public class NodesImpl implements Nodes
|
|||||||
else if (overwrite && nodeService.hasAspect(existingFile, ContentModel.ASPECT_VERSIONABLE))
|
else if (overwrite && nodeService.hasAspect(existingFile, ContentModel.ASPECT_VERSIONABLE))
|
||||||
{
|
{
|
||||||
// overwrite existing (versionable) file
|
// 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
|
else
|
||||||
{
|
{
|
||||||
@@ -3098,7 +3126,9 @@ public class NodesImpl implements Nodes
|
|||||||
versionMajor = versioningEnabled ? versionMajor : null;
|
versionMajor = versioningEnabled ? versionMajor : null;
|
||||||
|
|
||||||
// Create a new file.
|
// 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
|
// Create the response
|
||||||
final Node fileNode = getFolderOrDocumentFullInfo(nodeRef, parentNodeRef, nodeTypeQName, parameters);
|
final Node fileNode = getFolderOrDocumentFullInfo(nodeRef, parentNodeRef, nodeTypeQName, parameters);
|
||||||
@@ -3115,6 +3145,10 @@ public class NodesImpl implements Nodes
|
|||||||
{
|
{
|
||||||
throw new PermissionDeniedException(ade.getMessage());
|
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
|
* 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,
|
private NodeRef createNewFile(NodeRef parentNodeRef, String fileName, QName nodeType,
|
||||||
Boolean versionMajor, String versionComment)
|
Content content, StorageClassSet storageClassSet, Map<QName, Serializable> props,
|
||||||
|
QName assocTypeQName, Parameters params, Boolean versionMajor, String versionComment)
|
||||||
{
|
{
|
||||||
NodeRef nodeRef = createNodeImpl(parentNodeRef, fileName, nodeType, props, assocTypeQName);
|
NodeRef nodeRef = createNodeImpl(parentNodeRef, fileName, nodeType, props, assocTypeQName);
|
||||||
|
|
||||||
@@ -3136,7 +3171,7 @@ public class NodesImpl implements Nodes
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Write content
|
// Write content
|
||||||
writeContent(nodeRef, fileName, content.getInputStream(), true);
|
writeContent(nodeRef, fileName, content.getInputStream(), true, storageClassSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((versionMajor != null) || (versionComment != null))
|
if ((versionMajor != null) || (versionComment != null))
|
||||||
@@ -3214,6 +3249,21 @@ public class NodesImpl implements Nodes
|
|||||||
return renditions;
|
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)
|
private void requestRenditions(Set<String> renditionNames, Node fileNode)
|
||||||
{
|
{
|
||||||
if (renditionNames != null)
|
if (renditionNames != null)
|
||||||
|
@@ -27,6 +27,8 @@ package org.alfresco.rest.api.model;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.repo.content.StorageClassSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of content info
|
* Representation of content info
|
||||||
*
|
*
|
||||||
@@ -39,7 +41,7 @@ public class ContentInfo
|
|||||||
private String mimeTypeName;
|
private String mimeTypeName;
|
||||||
private Long sizeInBytes;
|
private Long sizeInBytes;
|
||||||
private String encoding;
|
private String encoding;
|
||||||
private Set<String> storageClasses;
|
private StorageClassSet storageClassSet;
|
||||||
|
|
||||||
public ContentInfo()
|
public ContentInfo()
|
||||||
{
|
{
|
||||||
@@ -53,13 +55,13 @@ public class ContentInfo
|
|||||||
this.encoding = encoding;
|
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.mimeType = mimeType;
|
||||||
this.mimeTypeName = mimeTypeName;
|
this.mimeTypeName = mimeTypeName;
|
||||||
this.sizeInBytes = sizeInBytes;
|
this.sizeInBytes = sizeInBytes;
|
||||||
this.encoding = encoding;
|
this.encoding = encoding;
|
||||||
this.storageClasses = storageClasses;
|
this.storageClassSet = storageClassSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
@@ -82,20 +84,21 @@ public class ContentInfo
|
|||||||
return encoding;
|
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
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "ContentInfo [mimeType=" + mimeType + ", mimeTypeName=" + mimeTypeName
|
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.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.repo.content.StorageClassSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic information about content. Typically used with HTTPServletResponse
|
* Basic information about content. Typically used with HTTPServletResponse
|
||||||
*/
|
*/
|
||||||
public interface ContentInfo extends BasicContentInfo{
|
public interface ContentInfo extends BasicContentInfo{
|
||||||
public long getLength();
|
public long getLength();
|
||||||
public Locale getLocale();
|
public Locale getLocale();
|
||||||
public Set<String> getStorageClasses();
|
public StorageClassSet getStorageClasses();
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,8 @@
|
|||||||
package org.alfresco.rest.framework.resource.content;
|
package org.alfresco.rest.framework.resource.content;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
|
||||||
|
import org.alfresco.repo.content.StorageClassSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic implementation of information about the returned content.
|
* Basic implementation of information about the returned content.
|
||||||
@@ -37,21 +38,21 @@ public class ContentInfoImpl implements ContentInfo
|
|||||||
private final String encoding;
|
private final String encoding;
|
||||||
private final long length;
|
private final long length;
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private final Set<String> storageClasses;
|
private final StorageClassSet storageClassSet;
|
||||||
|
|
||||||
public ContentInfoImpl(String mimeType, String encoding, long length, Locale locale)
|
public ContentInfoImpl(String mimeType, String encoding, long length, Locale locale)
|
||||||
{
|
{
|
||||||
this(mimeType, encoding, length, locale, null);
|
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();
|
super();
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.encoding = encoding;
|
this.encoding = encoding;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.storageClasses = storageClasses;
|
this.storageClassSet = storageClassSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -76,8 +77,8 @@ public class ContentInfoImpl implements ContentInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
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
|
@Test
|
||||||
public void testUpdateNodeConcurrentlyUsingInMemoryBacked() throws Exception
|
public void testUpdateNodeConcurrentlyUsingInMemoryBacked() throws Exception
|
||||||
{
|
{
|
||||||
@@ -4784,16 +4858,15 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
|
|||||||
{
|
{
|
||||||
setRequestContext(user1);
|
setRequestContext(user1);
|
||||||
|
|
||||||
// Create folder with an empty document
|
Document document = createTextFile(Nodes.PATH_MY, "file.txt",
|
||||||
String postUrl = createFolder();
|
"The quick brown fox jumps over the lazy dog.");
|
||||||
String docId = createDocument(postUrl);
|
|
||||||
|
|
||||||
Map params = new HashMap<>();
|
Map params = new HashMap<>();
|
||||||
params.put("include", "storageClasses");
|
params.put("include", "storageClasses");
|
||||||
|
|
||||||
// Update node
|
// Update node
|
||||||
Document dUpdate = new Document();
|
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);
|
Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
|
||||||
|
|
||||||
// Check if storageClasses are retrieved if 'include=storageClasses' is not sent in the request
|
// 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 java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.repo.content.StorageClassSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of content info (initially for client tests for File Folder API)
|
* Representation of content info (initially for client tests for File Folder API)
|
||||||
*
|
*
|
||||||
@@ -42,7 +44,7 @@ public class ContentInfo
|
|||||||
private String mimeTypeName;
|
private String mimeTypeName;
|
||||||
private Long sizeInBytes;
|
private Long sizeInBytes;
|
||||||
private String encoding;
|
private String encoding;
|
||||||
private Set<String> storageClasses;
|
private StorageClassSet storageClassSet;
|
||||||
|
|
||||||
public ContentInfo()
|
public ContentInfo()
|
||||||
{
|
{
|
||||||
@@ -80,14 +82,14 @@ public class ContentInfo
|
|||||||
this.encoding = encoding;
|
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)
|
public void expected(Object o)
|
||||||
@@ -100,7 +102,7 @@ public class ContentInfo
|
|||||||
AssertUtil.assertEquals("mimeTypeName", mimeTypeName, other.getMimeTypeName());
|
AssertUtil.assertEquals("mimeTypeName", mimeTypeName, other.getMimeTypeName());
|
||||||
AssertUtil.assertEquals("sizeInBytes", sizeInBytes, other.getSizeInBytes());
|
AssertUtil.assertEquals("sizeInBytes", sizeInBytes, other.getSizeInBytes());
|
||||||
AssertUtil.assertEquals("encoding", encoding, other.getEncoding());
|
AssertUtil.assertEquals("encoding", encoding, other.getEncoding());
|
||||||
AssertUtil.assertEquals("storageClasses", storageClasses, other.storageClasses);
|
AssertUtil.assertEquals("storageClasses", storageClassSet, other.storageClassSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,7 +113,7 @@ public class ContentInfo
|
|||||||
.append(", mimeTypeName=").append(mimeTypeName)
|
.append(", mimeTypeName=").append(mimeTypeName)
|
||||||
.append(", sizeInBytes=").append(sizeInBytes)
|
.append(", sizeInBytes=").append(sizeInBytes)
|
||||||
.append(", encoding=").append(encoding)
|
.append(", encoding=").append(encoding)
|
||||||
.append(", storageClasses=").append(storageClasses)
|
.append(", storageClasses=").append(storageClassSet)
|
||||||
.append(']');
|
.append(']');
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
@@ -449,9 +449,21 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa
|
|||||||
|
|
||||||
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update)
|
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)
|
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
|
// for this case, we just give back a valid URL into the content store
|
||||||
ContentWriter writer = store.getWriter(ctx);
|
ContentWriter writer = store.getWriter(ctx);
|
||||||
// Register the new URL for rollback cleanup
|
// Register the new URL for rollback cleanup
|
||||||
@@ -462,10 +474,38 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa
|
|||||||
|
|
||||||
// check for an existing URL - the get of the reader will perform type checking
|
// check for an existing URL - the get of the reader will perform type checking
|
||||||
ContentReader existingContentReader = getReader(nodeRef, propertyQName, false);
|
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
|
// get the content using the (potentially) existing content - the new content
|
||||||
// can be wherever the store decides.
|
// 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);
|
ContentWriter writer = store.getWriter(ctx);
|
||||||
// Register the new URL for rollback cleanup
|
// Register the new URL for rollback cleanup
|
||||||
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
||||||
|
@@ -1,30 +1,32 @@
|
|||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
* provided under the following open source license terms:
|
* provided under the following open source license terms:
|
||||||
*
|
*
|
||||||
* Alfresco is free software: you can redistribute it and/or modify
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* Alfresco is distributed in the hope that it will be useful,
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Lesser General Public License for more details.
|
* GNU Lesser General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.content;
|
package org.alfresco.repo.content;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
@@ -63,6 +65,29 @@ public class NodeContentContext extends ContentContext
|
|||||||
this.propertyQName = propertyQName;
|
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
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
@@ -151,6 +151,40 @@ public interface ContentService
|
|||||||
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update)
|
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update)
|
||||||
throws InvalidNodeRefException, InvalidTypeException;
|
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
|
* Gets a writer to a temporary location. The longevity of the stored
|
||||||
* temporary content is determined by the system.
|
* temporary content is determined by the system.
|
||||||
|
Reference in New Issue
Block a user