diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 98eb973ec9..087344e713 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -466,6 +466,7 @@ + diff --git a/source/java/org/alfresco/rest/api/Nodes.java b/source/java/org/alfresco/rest/api/Nodes.java index ea820c289c..d7bcbe5c22 100644 --- a/source/java/org/alfresco/rest/api/Nodes.java +++ b/source/java/org/alfresco/rest/api/Nodes.java @@ -208,4 +208,7 @@ public interface Nodes String PARAM_MIMETYPE = "mimeType"; String PARAM_SIZEINBYTES = "sizeInBytes"; String PARAM_NODETYPE = "nodeType"; + + String PARAM_VERSION_MAJOR = "majorVersion"; // true if major, false if minor + String PARAM_VERSION_COMMENT = "comment"; } diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index b42498faa0..8b91cbfcbf 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -38,12 +39,15 @@ import org.alfresco.model.ContentModel; import org.alfresco.model.QuickShareModel; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; +import org.alfresco.repo.action.executer.ContentMetadataExtracter; import org.alfresco.repo.content.ContentLimitViolationException; import org.alfresco.repo.model.Repository; import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.version.VersionModel; import org.alfresco.rest.antlr.WhereClauseParser; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.QuickShareLinks; @@ -64,6 +68,7 @@ import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; import org.alfresco.rest.framework.core.exceptions.RequestEntityTooLargeException; import org.alfresco.rest.framework.resource.content.BasicContentInfo; import org.alfresco.rest.framework.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.content.ContentInfoImpl; import org.alfresco.rest.framework.resource.content.NodeBinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Paging; @@ -104,6 +109,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.usage.ContentQuotaException; import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; @@ -120,7 +126,9 @@ import org.springframework.http.MediaType; * * Note: * This class was originally used for returning some basic node info when listing Favourites. - * It has now been re-purposed and extended to implement the new File Folder (RESTful) API. + * + * It has now been re-purposed and extended to implement the new Nodes (RESTful) API for + * managing files & folders, as well as custom node types. * * @author steveglover * @author janv @@ -151,6 +159,8 @@ public class NodesImpl implements Nodes private OwnableService ownableService; private AuthorityService authorityService; + private BehaviourFilter behaviourFilter; + // note: circular - Nodes/QuickShareLinks currently use each other (albeit for different methods) private QuickShareLinks quickShareLinks; @@ -198,6 +208,11 @@ public class NodesImpl implements Nodes this.sr = sr; } + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + public void setRepositoryHelper(Repository repositoryHelper) { this.repositoryHelper = repositoryHelper; @@ -1557,11 +1572,69 @@ public class NodesImpl implements Nodes throw new InvalidArgumentException("NodeId of content is expected: " + nodeRef.getId()); } + Boolean versionMajor = null; + String str = parameters.getParameter(PARAM_VERSION_MAJOR); + if (str != null) + { + versionMajor = new Boolean(str); + } + String versionComment = parameters.getParameter(PARAM_VERSION_COMMENT); + + return updateExistingFile(nodeRef, contentInfo, stream, parameters, versionMajor, versionComment); + } + + private Node updateExistingFile(NodeRef nodeRef, BasicContentInfo contentInfo, InputStream stream, Parameters parameters, Boolean versionMajor, String versionComment) + { + boolean isVersioned = versionService.isVersioned(nodeRef); + + behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); + try + { + writeContent(nodeRef, contentInfo, stream); + + if ((isVersioned) || (versionMajor != null) || (versionComment != null) ) + { + VersionType versionType = VersionType.MINOR; + if ((versionMajor != null) && (versionMajor == true)) + { + versionType = VersionType.MAJOR; + } + createVersion(nodeRef, isVersioned, versionType, versionComment); + } + + extractMetadata(nodeRef); + } + finally + { + behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); + } + + return getFolderOrDocumentFullInfo(nodeRef, null, null, parameters); + } + + private void writeContent(NodeRef nodeRef, BasicContentInfo contentInfo, InputStream stream) + { ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); setWriterContentType(writer, new ContentInfoWrapper(contentInfo), nodeRef, true); writer.putContent(stream); + } - return getFolderOrDocumentFullInfo(nodeRef, null, null, parameters); + protected void createVersion(NodeRef nodeRef, boolean isVersioned, VersionType versionType, String reason) + { + if (! isVersioned) + { + // Ensure the file is versionable (autoVersion = true, autoVersionProps = false) + ensureVersioningEnabled(nodeRef, true, false); + } + + Map versionProperties = new HashMap<>(2); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, versionType); + if (reason != null) + { + versionProperties.put(VersionModel.PROP_DESCRIPTION, reason); + } + + versionService.createVersion(nodeRef, versionProperties); } private void setWriterContentType(ContentWriter writer, ContentInfoWrapper contentInfo, NodeRef nodeRef, boolean guessEncodingIfNull) @@ -1591,6 +1664,7 @@ public class NodesImpl implements Nodes } } + @Override public Node upload(String parentFolderNodeId, FormData formData, Parameters parameters) { @@ -1611,6 +1685,8 @@ public class NodesImpl implements Nodes boolean autoRename = false; QName nodeTypeQName = null; boolean overwrite = false; // If a fileName clashes for a versionable file + Boolean majorVersion = null; + String versionComment = null; Map qnameStrProps = new HashMap<>(); Map properties = null; @@ -1642,9 +1718,17 @@ public class NodesImpl implements Nodes } break; - // case "overwrite": - // overwrite = Boolean.valueOf(field.getValue()); - // break; + case "overwrite": + overwrite = Boolean.valueOf(field.getValue()); + break; + + case "majorversion": + majorVersion = Boolean.valueOf(field.getValue()); + break; + + case "comment": + versionComment = getStringOrNull(field.getValue()); + break; default: { @@ -1684,28 +1768,23 @@ public class NodesImpl implements Nodes NodeRef existingFile = nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, fileName); if (existingFile != null) { + // TODO throw 400 error if both autoRename and overwrite are true ? + // File already exists, decide what to do if (autoRename) { + // attempt to find a unique name fileName = findUniqueName(parentNodeRef, fileName); } - // TODO uncomment when we decide on uploading a new version vs overwriting - // else if (overwrite && nodeService.hasAspect(existingFile, ContentModel.ASPECT_VERSIONABLE)) - // { - // // Upload component was configured to overwrite files if name clashes - // write(existingFile, content); - // - // // Extract the metadata (The overwrite policy controls - // // which if any parts of the document's properties are updated from this) - // extractMetadata(existingFile); - // - // // Do not clean formData temp files to - // // allow for retries. Temp files will be deleted later - // // when GC call DiskFileItem#finalize() method or by temp file cleaner. - // return createUploadResponse(parentNodeRef, existingFile); - // } + 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(existingFile, contentInfo, content.getInputStream(), parameters, majorVersion, versionComment); + } else { + // name clash (and no autoRename or overwrite) throw new ConstraintViolatedException(fileName + " already exists."); } } @@ -1814,10 +1893,12 @@ public class NodesImpl implements Nodes /** * Extracts the given node metadata asynchronously. + * + * The overwrite policy controls which if any parts of the document's properties are updated from this. */ private void extractMetadata(NodeRef nodeRef) { - final String actionName = "extract-metadata"; + final String actionName = ContentMetadataExtracter.EXECUTOR_NAME; ActionDefinition actionDef = actionService.getActionDefinition(actionName); if (actionDef != null) {