initial checkin

This commit is contained in:
2023-03-03 16:02:39 -05:00
commit 755271cc42
25 changed files with 2140 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
# Maven
target
pom.xml.versionsBackup
# Eclipse
.settings
.project
.classpath
# Visual Studio Code
.factorypath
.vscode

88
pom.xml Normal file
View File

@@ -0,0 +1,88 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>bulk-platform-module</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>bulk ACS Platform Module</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<alfresco.sdk.version>4.2.0</alfresco.sdk.version>
<alfresco.platform.version>6.2.0-ga</alfresco.platform.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>${alfresco.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Very popular, but not required, dependency -->
<!-- Provided as an example -->
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-repository</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jaxrs</artifactId>
<version>1.6.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.26</version>
<extensions>true</extensions>
<configuration>
<tiles>
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-rad-tile -->
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.0.0,2.0.0)</tile>
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile -->
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.0.0,2.0.0)</tile>
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.0.0,2.0.0)</tile> -->
</tiles>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>inteligr8-releases</id>
<url>http://repos.inteligr8.com/nexus/repository/inteligr8-public</url>
</repository>
<repository>
<id>alfresco-public</id>
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>inteligr8-releases</id>
<url>http://repos.inteligr8.com/nexus/repository/inteligr8-public</url>
</pluginRepository>
</pluginRepositories>
</project>

74
rad.ps1 Normal file
View File

@@ -0,0 +1,74 @@
function discoverArtifactId {
$script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate)
}
function rebuild {
echo "Rebuilding project ..."
mvn process-classes
}
function start_ {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad process-classes
}
function start_log {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad "-Ddocker.showLogs" process-classes
}
function stop_ {
discoverArtifactId
echo "Stopping Docker containers that supported rapid application development ..."
docker container ls --filter name=${ARTIFACT_ID}-*
echo "Stopping containers ..."
docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*)
echo "Removing containers ..."
docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*)
}
function tail_logs {
param (
$container
)
discoverArtifactId
docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container})
}
function list {
discoverArtifactId
docker container ls --filter name=${ARTIFACT_ID}-*
}
switch ($args[0]) {
"start" {
start_
}
"start_log" {
start_log
}
"stop" {
stop_
}
"restart" {
stop_
start_
}
"rebuild" {
rebuild
}
"tail" {
tail_logs $args[1]
}
"containers" {
list
}
default {
echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
}
}
echo "Completed!"

71
rad.sh Normal file
View File

@@ -0,0 +1,71 @@
#!/bin/sh
discoverArtifactId() {
ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate`
}
rebuild() {
echo "Rebuilding project ..."
mvn process-classes
}
start() {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad process-classes
}
start_log() {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad -Ddocker.showLogs process-classes
}
stop() {
discoverArtifactId
echo "Stopping Docker containers that supported rapid application development ..."
docker container ls --filter name=${ARTIFACT_ID}-*
echo "Stopping containers ..."
docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*`
echo "Removing containers ..."
docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*`
}
tail_logs() {
discoverArtifactId
docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1`
}
list() {
discoverArtifactId
docker container ls --filter name=${ARTIFACT_ID}-*
}
case "$1" in
start)
start
;;
start_log)
start_log
;;
stop)
stop
;;
restart)
stop
start
;;
rebuild)
rebuild
;;
tail)
tail_logs $2
;;
containers)
list
;;
*)
echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
exit 1
esac
echo "Completed!"

View File

@@ -0,0 +1,136 @@
/*
* This program is free software: you can redistribute it and/or modify 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 (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, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.alfresco.bulk;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.apache.tika.Tika;
import org.apache.tika.config.TikaConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inteligr8.alfresco.bulk.model.ContentDataBody;
public abstract class AbstractNodesWebScript extends AbstractWebScript {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${inteligr8.bulk.enableTika:false}")
private boolean enableTika;
@Value("${inteligr8.bulk.allowOctetStream:false}")
protected boolean allowOctetStream;
@Autowired
protected ContentService contentService;
@Autowired
protected DictionaryService dictionaryService;
@Autowired
protected MimetypeService mimeTypeService;
@Autowired
protected NamespaceService namespaceService;
@Autowired
protected NodeService nodeService;
@Autowired
protected BehaviourFilter behaviorFilter;
@Autowired
protected TransactionService txService;
protected final ObjectMapper om = new ObjectMapper();
protected final Collection<MediaType> jsonMediaTypes =
MediaType.parseMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_VALUE, "applicatin/*+json"));
protected List<ContentDataBody> wrapContents(ContentDataBody contentData, List<ContentDataBody> contents) {
List<ContentDataBody> allcontents = new ArrayList<>(contents == null ? 1 : (contents.size() + 1));
if (contentData != null)
allcontents.add(contentData);
if (contents != null)
allcontents.addAll(contents);
return allcontents;
}
protected void validateRequest(List<?> nodes) {
if (nodes == null || nodes.isEmpty())
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "At least one node is required");
}
protected String determineMimeType(QName contentPropName, ContentReader creader, String name) {
if (this.enableTika) {
this.logger.trace("Using Tika to detect the MIME type: {} => {}", creader.getContentUrl(), name);
try {
InputStream istream = creader.getContentInputStream();
try {
Tika tika = new Tika(TikaConfig.getDefaultConfig());
String mimeType = tika.detect(istream, name);
this.logger.trace("Tika detected MIME type: {} => {}", creader.getContentUrl(), mimeType);
if (!MediaType.APPLICATION_OCTET_STREAM_VALUE.equals(mimeType))
return mimeType;
} finally {
istream.close();
}
} catch (IOException ie) {
this.logger.warn("Tika MIME detection failed: {}: {}", creader.getContentUrl(), ie.getMessage());
}
}
if (!contentPropName.equals(ContentModel.PROP_CONTENT)) {
this.logger.debug("Unable to auto-detect a content property other than the default 'cm:content': {}", contentPropName);
return null;
}
int pos = name.lastIndexOf('.');
if (pos < 0) {
this.logger.debug("Unable to auto-detect a MIME type without a filename extension: {}", name);
return null;
}
String extension = name.substring(pos+1);
String mimeType = this.mimeTypeService.getMimetype(extension);
this.logger.debug("Detected file extension and MIME type: {} => {}", extension, mimeType);
return mimeType;
}
}

View File

@@ -0,0 +1,305 @@
/*
* This program is free software: you can redistribute it and/or modify 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 (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, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.alfresco.bulk;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import com.inteligr8.alfresco.bulk.model.AssociationBody;
import com.inteligr8.alfresco.bulk.model.ChildAssociationBody;
import com.inteligr8.alfresco.bulk.model.ContentDataBody;
import com.inteligr8.alfresco.bulk.model.NodeBodyCreateExt;
@Component(value = "webscript.com.inteligr8.alfresco.bulk.createNodes.post")
public class CreateNodesWebScript extends AbstractNodesWebScript {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException {
this.logger.trace("execute()");
String folderNodeId = req.getServiceMatch().getTemplateVars().get("folderNodeId");
MediaType contentType = this.validateAndGetContentType(req.getContentType());
boolean multipart = MediaType.MULTIPART_FORM_DATA.equals(contentType);
List<String> createdNodeIds;
if (multipart) {
throw new WebScriptException(HttpStatus.NOT_IMPLEMENTED.value(), "This service does not yet support multipart content");
} else {
List<NodeBodyCreateExt> nodes = this.parseRequest(req.getContent());
this.validateRequest(nodes);
createdNodeIds = this.createNodes(folderNodeId, nodes);
}
res.setStatus(HttpStatus.OK.value());
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
res.setContentEncoding("utf-8");
this.om.writeValue(res.getWriter(), createdNodeIds);
}
protected List<String> createNodes(String parentNodeId, List<NodeBodyCreateExt> nodes) {
RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() {
NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, parentNodeId);
if (!nodeService.exists(parentNodeRef))
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The folder node could not be found: {}", parentNodeId);
if (!dictionaryService.isSubClass(nodeService.getType(parentNodeRef), ContentModel.TYPE_FOLDER))
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The parent node must be a folder: {}", parentNodeId);
// allow cm:created/cm:modified to override
behaviorFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
List<String> createdNodeIds = new LinkedList<>();
try {
for (NodeBodyCreateExt node : nodes) {
String nodeId = createNode(parentNodeRef, node);
if (nodeId != null)
createdNodeIds.add(nodeId);
}
} catch (WebScriptException wse) {
return wse;
}
behaviorFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
return createdNodeIds;
}
};
Object response = this.txService.getRetryingTransactionHelper().doInTransaction(rtcallback, false, false);
if (response instanceof WebScriptException)
throw (WebScriptException) response;
return (List<String>) response;
}
protected String createNode(NodeRef parentNodeRef, NodeBodyCreateExt incomingNode) {
this.logger.debug("Attempting to create node: {} in {}", incomingNode.getName(), parentNodeRef);
Map<? extends String, ? extends Serializable> incomingProps = (Map<? extends String, ? extends Serializable>) incomingNode.getProperties();
String name = incomingNode.getName();
if (name == null) {
name = incomingProps == null ? null : (String) incomingProps.get(ContentModel.PROP_NAME.toPrefixString(this.namespaceService));
if (name == null)
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "A name or 'cm:name' property is required");
}
this.logger.trace("Using name: {}", name);
boolean hasContent = incomingNode.getContentData() != null || (incomingNode.getContents() != null && !incomingNode.getContents().isEmpty());
QName nodeType = incomingNode.getNodeType() == null
? (hasContent ? ContentModel.TYPE_CONTENT : ContentModel.TYPE_FOLDER)
: QName.createQName(incomingNode.getNodeType(), this.namespaceService);
this.logger.trace("Using node type: {}", nodeType);
Map<QName, Serializable> props = null;
if (incomingProps != null) {
props = new HashMap<>(incomingProps.size()+1);
for (Entry<? extends String, ? extends Serializable> prop : incomingProps.entrySet()) {
QName propName = QName.createQName(prop.getKey(), this.namespaceService);
props.put(propName, prop.getValue());
}
if (incomingNode.getName() != null)
props.put(ContentModel.PROP_NAME, name);
// audit is disable, so initialize if not set
if (!props.containsKey(ContentModel.PROP_CREATED))
props.put(ContentModel.PROP_CREATED, new Date());
if (!props.containsKey(ContentModel.PROP_MODIFIED))
props.put(ContentModel.PROP_MODIFIED, new Date());
if (!props.containsKey(ContentModel.PROP_CREATOR))
props.put(ContentModel.PROP_CREATOR, AuthenticationUtil.getFullyAuthenticatedUser());
if (!props.containsKey(ContentModel.PROP_MODIFIER))
props.put(ContentModel.PROP_MODIFIER, AuthenticationUtil.getFullyAuthenticatedUser());
}
this.logger.trace("Using properties: {}", props.keySet());
QName assocType = incomingNode.getAssociation() == null
? ContentModel.ASSOC_CONTAINS
: QName.createQName(incomingNode.getAssociation().getAssocType(), this.namespaceService);
QName qname = QName.createQNameWithValidLocalName(NamespaceService.CONTENT_MODEL_1_0_URI, name);
this.logger.trace("Using parent association type and name: {} => {}", props.keySet(), qname);
ChildAssociationRef parentRef = this.nodeService.createNode(parentNodeRef, assocType, qname, nodeType, props);
NodeRef nodeRef = parentRef.getChildRef();
this.logger.debug("Created node: {}", nodeRef);
this.addNodeAspects(nodeRef, incomingNode.getAspectNames());
this.addNodeAssociations(nodeRef, incomingNode.getTargets());
this.addNodeSecondaryAssociations(nodeRef, incomingNode.getSecondaryChildren());
this.addNodeContents(nodeRef, this.wrapContents(incomingNode.getContentData(), incomingNode.getContents()), name);
return nodeRef.getId();
}
private int addNodeAspects(NodeRef nodeRef, List<String> incomingAspects) {
int changes = 0;
if (incomingAspects == null)
return changes;
this.logger.debug("Adding aspects: {}: {}", nodeRef, incomingAspects);
for (String aspectName : incomingAspects) {
QName aspectQName = QName.createQName(aspectName, this.namespaceService);
this.nodeService.addAspect(nodeRef, aspectQName, null);
changes++;
}
return changes;
}
private int addNodeAssociations(NodeRef nodeRef, List<AssociationBody> incomingAssocs) {
int changes = 0;
if (incomingAssocs == null)
return changes;
this.logger.debug("Adding associations: {}: {}", nodeRef, incomingAssocs);
for (AssociationBody assoc : incomingAssocs) {
NodeRef targetNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, assoc.getTargetId());
QName assocType = QName.createQName(assoc.getAssocType(), this.namespaceService);
this.nodeService.createAssociation(nodeRef, targetNodeRef, assocType);
changes++;
}
return changes;
}
private int addNodeSecondaryAssociations(NodeRef nodeRef, List<ChildAssociationBody> incomingAssocs) {
int changes = 0;
if (incomingAssocs == null)
return changes;
this.logger.debug("Adding child associations: {}: {}", nodeRef, incomingAssocs);
for (ChildAssociationBody assoc : incomingAssocs) {
NodeRef childNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, assoc.getChildId());
ChildAssociationRef childParentRef = this.nodeService.getPrimaryParent(childNodeRef);
QName assocType = QName.createQName(assoc.getAssocType(), this.namespaceService);
this.nodeService.addChild(nodeRef, childNodeRef, assocType, childParentRef.getQName());
changes++;
}
return changes;
}
private int addNodeContents(NodeRef nodeRef, List<ContentDataBody> incomingContents, String name) {
int changes = 0;
if (incomingContents.isEmpty())
return changes;
Set<QName> contentPropsProcessed = new HashSet<>();
for (ContentDataBody content : incomingContents) {
QName contentPropName = content.getPropertyName() == null ?
ContentModel.PROP_CONTENT :
QName.createQName(content.getPropertyName(), this.namespaceService);
if (!contentPropsProcessed.add(contentPropName))
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The same content property was specified more than once on a node: " + nodeRef);
if (content.getContentUrl() == null)
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "In-place node creation requires a content URL: " + nodeRef);
// this will allow us to get the size without opening a stream
// it may also be used to determine the MIME type
ContentReader creader = this.contentService.getRawReader(content.getContentUrl());
if (content.getMimeType() == null) {
String mimeType = this.determineMimeType(contentPropName, creader, name);
if (mimeType == null)
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "In-place node creation requires a MIME type: " + nodeRef);
if (!this.allowOctetStream && mimeType.equals(MediaType.APPLICATION_OCTET_STREAM_VALUE))
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "MIME type '" + mimeType + "' is not allowed by configuration of this module");
content.setMimeType(mimeType);
}
ContentData contentData = new ContentData(content.getContentUrl(),
content.getMimeType(), creader.getSize(), content.getEncoding(), Locale.getDefault());
this.nodeService.setProperty(nodeRef, contentPropName, contentData);
changes++;
}
return changes;
}
private MediaType validateAndGetContentType(String contentType) {
contentType = StringUtils.trimToNull(contentType);
this.logger.debug("Received content type: {}", contentType);
if (contentType == null)
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "A content type is required");
MediaType incomingMediaType = MediaType.parseMediaType(contentType);
for (MediaType jsonMediaType : this.jsonMediaTypes) {
if (jsonMediaType.includes(incomingMediaType)) {
return MediaType.APPLICATION_JSON;
}
}
if (MediaType.MULTIPART_FORM_DATA.equals(incomingMediaType))
return incomingMediaType;
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "A JSON or multipart content type is required");
}
private List<NodeBodyCreateExt> parseRequest(Content content) throws IOException {
this.logger.debug("Received content of size: {}", content.getSize());
InputStream istream = content.getInputStream();
try {
return this.om.readValue(istream, new TypeReference<List<NodeBodyCreateExt>>() {});
} finally {
istream.close();
}
}
}

View File

@@ -0,0 +1,305 @@
/*
* This program is free software: you can redistribute it and/or modify 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 (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, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.alfresco.bulk;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import com.inteligr8.alfresco.bulk.model.AssociationBody;
import com.inteligr8.alfresco.bulk.model.ContentDataBody;
import com.inteligr8.alfresco.bulk.model.NodeBodyUpdateExt;
import com.inteligr8.alfresco.bulk.model.PermissionsBodyUpdate;
@Component(value = "webscript.com.inteligr8.alfresco.bulk.updateNodes.put")
public class UpdateNodesWebScript extends AbstractNodesWebScript {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException {
this.logger.trace("execute()");
this.validateContentType(req.getContentType());
List<NodeBodyUpdateExt> nodes = this.parseRequest(req.getContent());
this.validateRequest(nodes);
List<String> updatedNodeIds = this.updateNodes(nodes);
res.setStatus(HttpStatus.OK.value());
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
res.setContentEncoding("utf-8");
this.om.writeValue(res.getWriter(), updatedNodeIds);
}
protected List<String> updateNodes(List<NodeBodyUpdateExt> nodes) {
RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() {
List<String> updatedNodeIds = new LinkedList<>();
try {
for (NodeBodyUpdateExt node : nodes) {
if (updateNode(node))
updatedNodeIds.add(node.getId());
}
} catch (WebScriptException wse) {
return wse;
}
return updatedNodeIds;
}
};
Object response = this.txService.getRetryingTransactionHelper().doInTransaction(rtcallback, false, false);
if (response instanceof WebScriptException)
throw (WebScriptException) response;
return (List<String>) response;
}
protected boolean updateNode(NodeBodyUpdateExt node) {
NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, node.getId());
this.logger.debug("Attempting to update node: {}", nodeRef);
if (!this.nodeService.exists(nodeRef))
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The node could not be found: {}", node.getId());
this.updateNodeAspects(nodeRef, node.getAspectNames());
this.updateNodeType(nodeRef, node.getNodeType());
this.updateNodeProperties(nodeRef, node.getName(), node.getProperties(), node.isRemoveUnspecifiedProperties());
this.updateNodeAssociations(nodeRef, node.getTargets());
this.updateNodeContents(nodeRef, this.wrapContents(node.getContentData(), node.getContents()));
this.updateNodePermissions(nodeRef, node.getPermissions());
return true;
}
private int updateNodeAspects(NodeRef nodeRef, List<String> incomingAspects) {
int changes = 0;
if (incomingAspects == null)
return changes;
Set<QName> currentAspects = this.nodeService.getAspects(nodeRef);
this.logger.trace("Reconciling aspects: {}: {} => {}", nodeRef, currentAspects, incomingAspects);
for (String aspectName : incomingAspects) {
QName aspectQName = QName.createQName(aspectName, this.namespaceService);
if (!currentAspects.remove(aspectQName)) {
this.logger.debug("Adding aspect: {}: {}", nodeRef, aspectQName);
this.nodeService.addAspect(nodeRef, aspectQName, null);
changes++;
}
}
for (QName aspectQName : currentAspects) {
this.logger.debug("Removing aspect: {}: {}", nodeRef, aspectQName);
this.nodeService.removeAspect(nodeRef, aspectQName);
changes++;
}
return changes;
}
private boolean updateNodeType(NodeRef nodeRef, String incomingNodeTypeName) {
if (incomingNodeTypeName == null)
return false;
QName currentNodeType = this.nodeService.getType(nodeRef);
this.logger.trace("Reconciling node type: {}: {} => {}", nodeRef, currentNodeType, incomingNodeTypeName);
QName incomingNodeType = QName.createQName(incomingNodeTypeName, this.namespaceService);
if (!currentNodeType.equals(incomingNodeType)) {
this.logger.debug("Changing node type: {}: {}", nodeRef, incomingNodeType);
this.nodeService.setType(nodeRef, incomingNodeType);
return true;
}
return false;
}
private void updateNodeProperties(NodeRef nodeRef, String incomingName, Map<String, String> incomingProps, boolean removeUnspecProps) {
if (incomingName == null && incomingProps == null)
return;
Map<QName, Serializable> props;
if (incomingProps == null) {
props = new HashMap<>(1);
} else {
props = new HashMap<>(incomingProps.size()+1);
for (Entry<String, String> prop : incomingProps.entrySet()) {
QName propName = QName.createQName(prop.getKey(), this.namespaceService);
props.put(propName, prop.getValue());
}
}
if (incomingName != null)
props.put(ContentModel.PROP_NAME, incomingName);
this.logger.debug("Changing properties: {}: {}", nodeRef, props.keySet());
this.nodeService.addProperties(nodeRef, props);
if (removeUnspecProps) {
Map<QName, Serializable> currentProps = this.nodeService.getProperties(nodeRef);
for (QName propName : props.keySet())
currentProps.remove(propName);
for (QName propName : currentProps.keySet()) {
this.logger.trace("Removing unspecified property: {}: {}", nodeRef, propName);
this.nodeService.removeProperty(nodeRef, propName);
}
}
}
private int updateNodeAssociations(NodeRef nodeRef, List<AssociationBody> incomingAssocs) {
int changes = 0;
if (incomingAssocs == null)
return changes;
Set<AssociationRef> currentAssocRefs = new HashSet<>(this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
this.logger.trace("Reconciling associations: {}: {} => {}", nodeRef, currentAssocRefs, incomingAssocs);
for (AssociationBody assoc : incomingAssocs) {
NodeRef targetNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, assoc.getTargetId());
QName assocType = QName.createQName(assoc.getAssocType(), this.namespaceService);
AssociationRef incomingAssocRef = new AssociationRef(nodeRef, assocType, targetNodeRef);
if (!currentAssocRefs.remove(incomingAssocRef)) {
this.logger.debug("Adding association: {}: {}", nodeRef, incomingAssocRef);
this.nodeService.createAssociation(nodeRef, targetNodeRef, assocType);
changes++;
}
}
for (AssociationRef assocRef : currentAssocRefs) {
this.logger.debug("Removing association: {}: {}", nodeRef, assocRef);
this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName());
changes++;
}
return changes;
}
private int updateNodeContents(NodeRef nodeRef, List<ContentDataBody> incomingContents) {
int changes = 0;
if (incomingContents.isEmpty())
return changes;
Set<QName> contentPropsProcessed = new HashSet<>();
for (ContentDataBody content : incomingContents) {
QName contentPropName = content.getPropertyName() == null ?
ContentModel.PROP_CONTENT :
QName.createQName(content.getPropertyName(), this.namespaceService);
if (!contentPropsProcessed.add(contentPropName))
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The same content property was specified more than once on a node: " + nodeRef);
Object contentObj = this.nodeService.getProperty(nodeRef, contentPropName);
if (contentObj == null) {
throw new WebScriptException(HttpStatus.NOT_IMPLEMENTED.value(), "An update does not yet support new content");
} else if (!(contentObj instanceof ContentData)) {
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "A content property does not hold content data: " + contentPropName);
}
ContentData contentData = (ContentData) contentObj;
boolean changed = false;
if (content.getMimeType() != null && !content.getMimeType().equals(contentData.getMimetype())) {
if (!this.allowOctetStream && content.getMimeType().equals(MediaType.APPLICATION_OCTET_STREAM_VALUE))
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "MIME type '" + content.getMimeType() + "' is not allowed by configuration of this module");
this.logger.debug("Changing content MIME type: {}: {}", nodeRef, content.getMimeType());
contentData = ContentData.setMimetype(contentData, content.getMimeType());
changed = true;
}
if (content.getEncoding() != null && !content.getEncoding().equals(contentData.getEncoding())) {
this.logger.debug("Changing content encoding: {}: {}", nodeRef, content.getEncoding());
contentData = ContentData.setEncoding(contentData, content.getEncoding());
changed = true;
}
if (changed) {
this.nodeService.setProperty(nodeRef, contentPropName, contentData);
changes++;
}
}
return changes;
}
private int updateNodePermissions(NodeRef nodeRef, PermissionsBodyUpdate incomingPermissions) {
int changes = 0;
if (incomingPermissions == null)
return changes;
throw new WebScriptException(HttpStatus.NOT_IMPLEMENTED.value(), "An update does not yet support permissions");
}
private void validateContentType(String contentType) {
contentType = StringUtils.trimToNull(contentType);
this.logger.debug("Received content type: {}", contentType);
if (contentType == null)
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "A content type is required");
MediaType incomingMediaType = MediaType.parseMediaType(contentType);
for (MediaType jsonMediaType : this.jsonMediaTypes) {
if (jsonMediaType.includes(incomingMediaType)) {
return;
}
}
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "A JSON content type is required");
}
private List<NodeBodyUpdateExt> parseRequest(Content content) throws IOException {
this.logger.debug("Received content of size: {}", content.getSize());
InputStream istream = content.getInputStream();
try {
return this.om.readValue(istream, new TypeReference<List<NodeBodyUpdateExt>>() {});
} finally {
istream.close();
}
}
}

View File

@@ -0,0 +1,73 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class AssociationBody {
@ApiModelProperty(required = true, value = "")
private String targetId = null;
@ApiModelProperty(required = true, value = "")
private String assocType = null;
/**
* Get targetId
* @return targetId
**/
@JsonProperty("targetId")
public String getTargetId() {
return targetId;
}
public void setTargetId(String targetId) {
this.targetId = targetId;
}
public AssociationBody targetId(String targetId) {
this.targetId = targetId;
return this;
}
/**
* Get assocType
* @return assocType
**/
@JsonProperty("assocType")
public String getAssocType() {
return assocType;
}
public void setAssocType(String assocType) {
this.assocType = assocType;
}
public AssociationBody assocType(String assocType) {
this.assocType = assocType;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class AssociationBody {\n");
sb.append(" targetId: ").append(toIndentedString(targetId)).append("\n");
sb.append(" assocType: ").append(toIndentedString(assocType)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,73 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class ChildAssociationBody {
@ApiModelProperty(required = true, value = "")
private String childId = null;
@ApiModelProperty(required = true, value = "")
private String assocType = null;
/**
* Get childId
* @return childId
**/
@JsonProperty("childId")
public String getChildId() {
return childId;
}
public void setChildId(String childId) {
this.childId = childId;
}
public ChildAssociationBody childId(String childId) {
this.childId = childId;
return this;
}
/**
* Get assocType
* @return assocType
**/
@JsonProperty("assocType")
public String getAssocType() {
return assocType;
}
public void setAssocType(String assocType) {
this.assocType = assocType;
}
public ChildAssociationBody assocType(String assocType) {
this.assocType = assocType;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ChildAssociationBody {\n");
sb.append(" childId: ").append(toIndentedString(childId)).append("\n");
sb.append(" assocType: ").append(toIndentedString(assocType)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,73 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ContentDataBody {
@JsonProperty(defaultValue = "cm:content")
private String propertyName;
@JsonProperty
private String mimeType;
@JsonProperty
private String encoding;
@JsonProperty
private String contentUrl;
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public ContentDataBody propertyName(String propertyName) {
this.propertyName = propertyName;
return this;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public ContentDataBody mimeType(String mimeType) {
this.mimeType = mimeType;
return this;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public ContentDataBody encoding(String encoding) {
this.encoding = encoding;
return this;
}
public String getContentUrl() {
return contentUrl;
}
public void setContentUrl(String contentUrl) {
this.contentUrl = contentUrl;
}
public ContentDataBody contentUrl(String contentUrl) {
this.contentUrl = contentUrl;
return this;
}
}

View File

@@ -0,0 +1,230 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import java.util.Map;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class NodeBodyCreate {
@ApiModelProperty(required = true, value = "The name must not contain spaces or the following special characters: * \" < > \\ / ? : and |. The character . must not be used at the end of the name. ")
/**
* The name must not contain spaces or the following special characters: * \" < > \\ / ? : and |. The character . must not be used at the end of the name.
**/
private String name = null;
@ApiModelProperty(required = true, value = "")
private String nodeType = null;
@ApiModelProperty(value = "")
private List<String> aspectNames = null;
@ApiModelProperty(value = "")
private Map<String, String> properties = null;
@ApiModelProperty(value = "")
private String relativePath = null;
@ApiModelProperty(value = "")
private NodeBodyCreateAssociation association = null;
@ApiModelProperty(value = "")
private List<ChildAssociationBody> secondaryChildren = null;
@ApiModelProperty(value = "")
private List<AssociationBody> targets = null;
/**
* The name must not contain spaces or the following special characters: * \&quot; &lt; &gt; \\ / ? : and |. The character . must not be used at the end of the name.
* @return name
**/
@JsonProperty("name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public NodeBodyCreate name(String name) {
this.name = name;
return this;
}
/**
* Get nodeType
* @return nodeType
**/
@JsonProperty("nodeType")
public String getNodeType() {
return nodeType;
}
public void setNodeType(String nodeType) {
this.nodeType = nodeType;
}
public NodeBodyCreate nodeType(String nodeType) {
this.nodeType = nodeType;
return this;
}
/**
* Get aspectNames
* @return aspectNames
**/
@JsonProperty("aspectNames")
public List<String> getAspectNames() {
return aspectNames;
}
public void setAspectNames(List<String> aspectNames) {
this.aspectNames = aspectNames;
}
public NodeBodyCreate aspectNames(List<String> aspectNames) {
this.aspectNames = aspectNames;
return this;
}
public NodeBodyCreate addAspectNamesItem(String aspectNamesItem) {
this.aspectNames.add(aspectNamesItem);
return this;
}
/**
* Get properties
* @return properties
**/
@JsonProperty("properties")
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public NodeBodyCreate properties(Map<String, String> properties) {
this.properties = properties;
return this;
}
public NodeBodyCreate putPropertiesItem(String key, String propertiesItem) {
this.properties.put(key, propertiesItem);
return this;
}
/**
* Get relativePath
* @return relativePath
**/
@JsonProperty("relativePath")
public String getRelativePath() {
return relativePath;
}
public void setRelativePath(String relativePath) {
this.relativePath = relativePath;
}
public NodeBodyCreate relativePath(String relativePath) {
this.relativePath = relativePath;
return this;
}
/**
* Get association
* @return association
**/
@JsonProperty("association")
public NodeBodyCreateAssociation getAssociation() {
return association;
}
public void setAssociation(NodeBodyCreateAssociation association) {
this.association = association;
}
public NodeBodyCreate association(NodeBodyCreateAssociation association) {
this.association = association;
return this;
}
/**
* Get secondaryChildren
* @return secondaryChildren
**/
@JsonProperty("secondaryChildren")
public List<ChildAssociationBody> getSecondaryChildren() {
return secondaryChildren;
}
public void setSecondaryChildren(List<ChildAssociationBody> secondaryChildren) {
this.secondaryChildren = secondaryChildren;
}
public NodeBodyCreate secondaryChildren(List<ChildAssociationBody> secondaryChildren) {
this.secondaryChildren = secondaryChildren;
return this;
}
public NodeBodyCreate addSecondaryChildrenItem(ChildAssociationBody secondaryChildrenItem) {
this.secondaryChildren.add(secondaryChildrenItem);
return this;
}
/**
* Get targets
* @return targets
**/
@JsonProperty("targets")
public List<AssociationBody> getTargets() {
return targets;
}
public void setTargets(List<AssociationBody> targets) {
this.targets = targets;
}
public NodeBodyCreate targets(List<AssociationBody> targets) {
this.targets = targets;
return this;
}
public NodeBodyCreate addTargetsItem(AssociationBody targetsItem) {
this.targets.add(targetsItem);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class NodeBodyCreate {\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" nodeType: ").append(toIndentedString(nodeType)).append("\n");
sb.append(" aspectNames: ").append(toIndentedString(aspectNames)).append("\n");
sb.append(" properties: ").append(toIndentedString(properties)).append("\n");
sb.append(" relativePath: ").append(toIndentedString(relativePath)).append("\n");
sb.append(" association: ").append(toIndentedString(association)).append("\n");
sb.append(" secondaryChildren: ").append(toIndentedString(secondaryChildren)).append("\n");
sb.append(" targets: ").append(toIndentedString(targets)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,51 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class NodeBodyCreateAssociation {
@ApiModelProperty(value = "")
private String assocType = null;
/**
* Get assocType
* @return assocType
**/
@JsonProperty("assocType")
public String getAssocType() {
return assocType;
}
public void setAssocType(String assocType) {
this.assocType = assocType;
}
public NodeBodyCreateAssociation assocType(String assocType) {
this.assocType = assocType;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class NodeBodyCreateAssociation {\n");
sb.append(" assocType: ").append(toIndentedString(assocType)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,49 @@
package com.inteligr8.alfresco.bulk.model;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@NotThreadSafe
@JsonIgnoreProperties(ignoreUnknown = true)
public class NodeBodyCreateExt extends NodeBodyCreate {
@JsonProperty
private ContentDataBody contentData;
@JsonProperty
private List<ContentDataBody> contents;
public ContentDataBody getContentData() {
return contentData;
}
public void setContentData(ContentDataBody contentData) {
this.contentData = contentData;
}
public NodeBodyCreateExt contentData(ContentDataBody contentData) {
this.contentData = contentData;
return this;
}
public List<ContentDataBody> getContents() {
return contents;
}
public void setContents(List<ContentDataBody> contents) {
this.contents = contents;
}
public NodeBodyCreateExt addContent(ContentDataBody contentData) {
if (this.contents == null)
this.contents = new LinkedList<>();
this.contents.add(contentData);
return this;
}
}

View File

@@ -0,0 +1,154 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import java.util.Map;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class NodeBodyUpdate {
@ApiModelProperty(value = "The name must not contain spaces or the following special characters: * \" < > \\ / ? : and |. The character . must not be used at the end of the name. ")
/**
* The name must not contain spaces or the following special characters: * \" < > \\ / ? : and |. The character . must not be used at the end of the name.
**/
private String name = null;
@ApiModelProperty(value = "")
private String nodeType = null;
@ApiModelProperty(value = "")
private List<String> aspectNames = null;
@ApiModelProperty(value = "")
private Map<String, String> properties = null;
@ApiModelProperty(value = "")
private PermissionsBodyUpdate permissions = null;
/**
* The name must not contain spaces or the following special characters: * \&quot; &lt; &gt; \\ / ? : and |. The character . must not be used at the end of the name.
* @return name
**/
@JsonProperty("name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public NodeBodyUpdate name(String name) {
this.name = name;
return this;
}
/**
* Get nodeType
* @return nodeType
**/
@JsonProperty("nodeType")
public String getNodeType() {
return nodeType;
}
public void setNodeType(String nodeType) {
this.nodeType = nodeType;
}
public NodeBodyUpdate nodeType(String nodeType) {
this.nodeType = nodeType;
return this;
}
/**
* Get aspectNames
* @return aspectNames
**/
@JsonProperty("aspectNames")
public List<String> getAspectNames() {
return aspectNames;
}
public void setAspectNames(List<String> aspectNames) {
this.aspectNames = aspectNames;
}
public NodeBodyUpdate aspectNames(List<String> aspectNames) {
this.aspectNames = aspectNames;
return this;
}
public NodeBodyUpdate addAspectNamesItem(String aspectNamesItem) {
this.aspectNames.add(aspectNamesItem);
return this;
}
/**
* Get properties
* @return properties
**/
@JsonProperty("properties")
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public NodeBodyUpdate properties(Map<String, String> properties) {
this.properties = properties;
return this;
}
public NodeBodyUpdate putPropertiesItem(String key, String propertiesItem) {
this.properties.put(key, propertiesItem);
return this;
}
/**
* Get permissions
* @return permissions
**/
@JsonProperty("permissions")
public PermissionsBodyUpdate getPermissions() {
return permissions;
}
public void setPermissions(PermissionsBodyUpdate permissions) {
this.permissions = permissions;
}
public NodeBodyUpdate permissions(PermissionsBodyUpdate permissions) {
this.permissions = permissions;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class NodeBodyUpdate {\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" nodeType: ").append(toIndentedString(nodeType)).append("\n");
sb.append(" aspectNames: ").append(toIndentedString(aspectNames)).append("\n");
sb.append(" properties: ").append(toIndentedString(properties)).append("\n");
sb.append(" permissions: ").append(toIndentedString(permissions)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,99 @@
package com.inteligr8.alfresco.bulk.model;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@NotThreadSafe
@JsonIgnoreProperties(ignoreUnknown = true)
public class NodeBodyUpdateExt extends NodeBodyUpdate {
@JsonProperty
private String id;
@JsonProperty
private ContentDataBody contentData;
@JsonProperty
private List<ContentDataBody> contents;
@JsonProperty
private List<AssociationBody> targets;
@JsonProperty(defaultValue = "false")
private boolean removeUnspecifiedProperties;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public NodeBodyUpdateExt id(String id) {
this.id = id;
return this;
}
public ContentDataBody getContentData() {
return contentData;
}
public void setContentData(ContentDataBody contentData) {
this.contentData = contentData;
}
public NodeBodyUpdateExt contentData(ContentDataBody contentData) {
this.contentData = contentData;
return this;
}
public List<ContentDataBody> getContents() {
return contents;
}
public void setContents(List<ContentDataBody> contents) {
this.contents = contents;
}
public NodeBodyUpdateExt addContent(ContentDataBody contentData) {
if (this.contents == null)
this.contents = new LinkedList<>();
this.contents.add(contentData);
return this;
}
public List<AssociationBody> getTargets() {
return targets;
}
public void setTargets(List<AssociationBody> targets) {
this.targets = targets;
}
public NodeBodyUpdateExt addTarget(AssociationBody target) {
if (this.targets == null)
this.targets = new LinkedList<>();
this.targets.add(target);
return this;
}
public boolean isRemoveUnspecifiedProperties() {
return removeUnspecifiedProperties;
}
public void setRemoveUnspecifiedProperties(boolean removeUnspecifiedProperties) {
this.removeUnspecifiedProperties = removeUnspecifiedProperties;
}
public NodeBodyUpdateExt removeUnspecifiedProperties(boolean removeUnspecifiedProperties) {
this.removeUnspecifiedProperties = removeUnspecifiedProperties;
return this;
}
}

View File

@@ -0,0 +1,134 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class PermissionElement {
@ApiModelProperty(value = "")
private String authorityId = null;
@ApiModelProperty(value = "")
private String name = null;
@XmlType(name="AccessStatusEnum")
@XmlEnum(String.class)
public enum AccessStatusEnum {
@XmlEnumValue("ALLOWED") ALLOWED(String.valueOf("ALLOWED")), @XmlEnumValue("DENIED") DENIED(String.valueOf("DENIED"));
private String value;
AccessStatusEnum (String v) {
value = v;
}
public String value() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
public static AccessStatusEnum fromValue(String v) {
for (AccessStatusEnum b : AccessStatusEnum.values()) {
if (String.valueOf(b.value).equals(v)) {
return b;
}
}
return null;
}
}
@ApiModelProperty(value = "")
private AccessStatusEnum accessStatus = AccessStatusEnum.ALLOWED;
/**
* Get authorityId
* @return authorityId
**/
@JsonProperty("authorityId")
public String getAuthorityId() {
return authorityId;
}
public void setAuthorityId(String authorityId) {
this.authorityId = authorityId;
}
public PermissionElement authorityId(String authorityId) {
this.authorityId = authorityId;
return this;
}
/**
* Get name
* @return name
**/
@JsonProperty("name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PermissionElement name(String name) {
this.name = name;
return this;
}
/**
* Get accessStatus
* @return accessStatus
**/
@JsonProperty("accessStatus")
public String getAccessStatus() {
if (accessStatus == null) {
return null;
}
return accessStatus.value();
}
public void setAccessStatus(AccessStatusEnum accessStatus) {
this.accessStatus = accessStatus;
}
public PermissionElement accessStatus(AccessStatusEnum accessStatus) {
this.accessStatus = accessStatus;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class PermissionElement {\n");
sb.append(" authorityId: ").append(toIndentedString(authorityId)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" accessStatus: ").append(toIndentedString(accessStatus)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,79 @@
package com.inteligr8.alfresco.bulk.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class PermissionsBodyUpdate {
@ApiModelProperty(value = "")
private Boolean isInheritanceEnabled = null;
@ApiModelProperty(value = "")
private List<PermissionElement> locallySet = null;
/**
* Get isInheritanceEnabled
* @return isInheritanceEnabled
**/
@JsonProperty("isInheritanceEnabled")
public Boolean isIsInheritanceEnabled() {
return isInheritanceEnabled;
}
public void setIsInheritanceEnabled(Boolean isInheritanceEnabled) {
this.isInheritanceEnabled = isInheritanceEnabled;
}
public PermissionsBodyUpdate isInheritanceEnabled(Boolean isInheritanceEnabled) {
this.isInheritanceEnabled = isInheritanceEnabled;
return this;
}
/**
* Get locallySet
* @return locallySet
**/
@JsonProperty("locallySet")
public List<PermissionElement> getLocallySet() {
return locallySet;
}
public void setLocallySet(List<PermissionElement> locallySet) {
this.locallySet = locallySet;
}
public PermissionsBodyUpdate locallySet(List<PermissionElement> locallySet) {
this.locallySet = locallySet;
return this;
}
public PermissionsBodyUpdate addLocallySetItem(PermissionElement locallySetItem) {
this.locallySet.add(locallySetItem);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class PermissionsBodyUpdate {\n");
sb.append(" isInheritanceEnabled: ").append(toIndentedString(isInheritanceEnabled)).append("\n");
sb.append(" locallySet: ").append(toIndentedString(locallySet)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private static String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@@ -0,0 +1,18 @@
<!-- Documentation: https://docs.alfresco.com/content-services/6.2/develop/reference/web-scripts-ref -->
<webscript>
<shortname>Create Nodes</shortname>
<description><![CDATA[
<p>This REST API creates a set of nodes in a single transaction.
It extends the NodeBodyCreate model of the ACS Public REST API for maximum reusability.
The extension allows for creation of associations and in-place content specification.</p>
<p>As with any bulk transaction, if this operation fails on any one node, all changes will rollback.</p>
<p>TODO</p>
]]></description>
<url>/inteligr8/bulk/node/{folderNodeId}/nodes</url>
<url>/inteligr8/bulk/nodes/{folderNodeId}</url>
<format default="json"></format>
<authentication>user</authentication>
<family>Inteligr8 Bulk</family>
</webscript>

View File

@@ -0,0 +1,18 @@
<!-- Documentation: https://docs.alfresco.com/content-services/6.2/develop/reference/web-scripts-ref -->
<webscript>
<shortname>Update Nodes</shortname>
<description><![CDATA[
<p>This REST API updates a set of nodes in a single transaction.
It extends the NodeBodyUpdate model of the ACS Public REST API for maximum reusability.
The extension allows for updates to associations and content meta-data.
It also supports the update of node types, regardless of whether the update follows the specialization hierarchy.</p>
<p>As with any bulk transaction, if this operation fails on any one node, all changes will rollback.</p>
<p>TODO</p>
]]></description>
<url>/inteligr8/bulk/nodes</url>
<format default="json"></format>
<authentication>user</authentication>
<family>Inteligr8 Bulk</family>
</webscript>

View File

@@ -0,0 +1,8 @@
# Enable to use Tika to synchronously detect the MIME type when MIME type is not provided
# This opens a stream to the binary file and reads at least part of it to help determine the MIME type
# This will slow down imports when the filename extension may be enough to determine the MIME type
inteligr8.bulk.enableTika=false
# Enable to allow content to be undetermined, which means the MIME type would be 'application/octet-stream'.
inteligr8.bulk.allowOctetStream=false

View File

@@ -0,0 +1 @@
log4j.logger.com.inteligr8.alfresco.bulk=info

View File

@@ -0,0 +1,13 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Use this file for beans to be loaded in whatever order Alfresco/Spring decides -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- Enable Spring annotation scanning for classes in package -->
<context:component-scan base-package="com.inteligr8.alfresco.bulk" />
</beans>

View File

@@ -0,0 +1,4 @@
module.id=${project.artifactId}
module.title=${project.name}
module.description=${project.description}
module.version=${project.version}

View File

@@ -0,0 +1,8 @@
# Module debugging
log4j.logger.com.inteligr8.alfresco.bulk=trace
# WebScript debugging
log4j.logger.org.springframework.extensions.webscripts.ScriptLogger=debug
# non-WebScript JavaScript execution debugging
log4j.logger.org.alfresco.repo.jscript.ScriptLogger=debug

View File

@@ -0,0 +1,63 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<beans>
<!--
To support hot reloading of server side Javascript files in Share, we have to turn on development mode.
This setting will tell the Rhinoscript Processor not to compile and cache the JS files.
Cool, we can now change server side JS files and have the changes picked up,
without having to restart or refresh web scripts.
But… Due to a known bug in the Surf framework (ALF-9970) this will break the admin consoles in Share.
Override this bean and disable javascript compilation so that webscripts can be hot reloaded.
We have changed the 'compile' property from true to false.
-->
<bean id="javaScriptProcessor" class="org.alfresco.repo.jscript.RhinoScriptProcessor" init-method="register">
<property name="name">
<value>javascript</value>
</property>
<property name="extension">
<value>js</value>
</property>
<!-- Do not "compile javascript and cache compiled scripts" -->
<property name="compile">
<value>false</value>
</property>
<!-- allow sharing of sealed scopes for performance -->
<!-- disable to give each script it's own new scope which can be extended -->
<property name="shareSealedScopes">
<value>true</value>
</property>
<property name="scriptService">
<ref bean="scriptService"/>
</property>
<!-- Creates ScriptNodes which require the ServiceRegistry -->
<property name="serviceRegistry">
<ref bean="ServiceRegistry"/>
</property>
<property name="storeUrl">
<value>${spaces.store}</value>
</property>
<property name="storePath">
<value>${spaces.company_home.childname}</value>
</property>
</bean>
</beans>