If you have to change the content type (basically cm:content to a custom content type), then there is an OOTB action provided in Share document library.
This is very simple use case. But there could be a situation where you have multiple custom content types or folder types such as:
<types> <type name="demo:whitePaper"> <title>WhitePaper document</title> <description>A WhitePaper document</description> <parent>cm:content</parent> </type> <type name="demo:supportingDoc"> <title>Supporting document</title> <description>A supporting document</description> <parent>cm:content</parent> </type> </types>Here, both demo:whitePaper and demo:supportingDoc custom content types are defined by extending "cm:content" type.
-----------------------------------------------------------------With OOTB box change type action, if you have created a content/file which is of type "cm:content" then
you can change the type from cm:content to demo:whitePaper or demo:supportingDoc based on the configuration made
in share config (share-config-custom.xml).
Example of share-config-custom.xml config:
<config evaluator="string-compare" condition="DocumentLibrary"> <aspects> <visible/> <addable/> <removable/> </aspects> <types> <type name="cm:content"> <subtype name="demo:whitePaper"/> <subtype name="demo:supportingDoc"/> </type> </types> </config>Change Type action uses a repository webscript, it calls:
Document List Component - type submit at "/slingshot/doclib/type/node/{store_type}/{store_id}/{id}" url.
But if you have a requirement to allow changing from demo:whitePaper to demo:supportingDoc or vice-versa,
then OOTB box action will not work. However, you can create a custom webscript/action to achieve this use case
or extend the OOTB "Document List Component - type submit" webscript to allow handling custom content types.
Here, we will try to extend the "Document List Component - type submit" webscript to achieve our goal.
Follow the below given steps to extend the Change Type webscript as mentioned above:
- Create "type.post.desc.xml" webscript definition file under extension/templates directory in following directory structure:
extension/templates/webscripts/org/alfresco/slingshot/documentlibrary/type.post.desc.xml
- Add following content in the "type.post.desc.xml" file:
<!-- Extended the webScript for custom handling of change-type action --> <webscript> <shortname>type</shortname> <description>Document List Component - type submit</description> <url>/slingshot/doclib/type/node/{store_type}/{store_id}/{id}</url> <format default="json">argument</format> <authentication>user</authentication> <transaction>required</transaction> <lifecycle>internal</lifecycle> </webscript>
- Create a java class, name it e.g.: "ChangeTypeWebscript",
- Add following code in this class:
/* * Created By: Abhinav Kumar Mishra * Copyright © 2020. Abhinav Kumar Mishra. * All rights reserved. * * Licensed 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. */ package com.github.abhinavmishra14.webscript; import java.io.IOException; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.extensions.webscripts.AbstractWebScript; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptResponse; /** * The Class ChangeTypeWebscript.<br> * Extension of OOTB change type WebScript. */ public class ChangeTypeWebscript extends AbstractWebScript { /** The Constant LOGGER. */ private static final Logger LOGGER = LoggerFactory.getLogger(ChangeTypeWebscript.class); /** The Constant TYPE_PARAM_KEY. */ private static final String TYPE_PARAM_KEY = "type"; /** The Constant STORE_TYPE. */ private static final String STORE_TYPE = "store_type"; /** The Constant STORE_ID. */ private static final String STORE_ID = "store_id"; /** The Constant PARAM_ID. */ private static final String PARAM_ID = "id"; /** The Constant CONTENT_LENGTH. */ private static final String CONTENT_LENGTH = "Content-Length"; /** The Constant ENCODING_UTF_8. */ private static final String ENCODING_UTF_8 = "UTF-8"; /** The Constant CURRENT_KEY. */ private static final String CURRENT_KEY = "current"; /** The Constant CACHE_CONTROL. */ private static final String CACHE_CONTROL = "cache-Control"; /** The Constant NO_CACHE. */ private static final String NO_CACHE = "no-cache"; /** The Constant EXPIRES. */ private static final String EXPIRES = "Expires"; /** The Constant PRAGMA. */ private static final String PRAGMA = "Pragma"; /** The namespace service. */ private final transient NamespaceService namespaceService; /** The node service. */ private final transient NodeService nodeService; /** * The Constructor. * * @param namespaceService the namespace service * @param nodeService the node service */ public ChangeTypeWebscript(final NamespaceService namespaceService, final NodeService nodeService) { super(); this.namespaceService = namespaceService; this.nodeService = nodeService; } /* * (non-Javadoc) * * @see org.springframework.extensions.webscripts.WebScript#execute(org. * springframework.extensions.webscripts.WebScriptRequest, * org.springframework.extensions.webscripts.WebScriptResponse) */ @Override public void execute(final WebScriptRequest request, final WebScriptResponse response) throws IOException { QName targetType = null; try { final JSONObject requestPayload = new JSONObject(request.getContent().getContent()); targetType = getTargetType(requestPayload); LOGGER.info("Changing type, the targetType is: {}", targetType); final NodeRef nodeRef = getNodeRef(request); nodeService.setType(nodeRef, targetType); final QName changedType = nodeService.getType(nodeRef); writeResponse(response, changedType.getPrefixedQName(namespaceService)); } catch (JSONException excp) { LOGGER.error("Error occurred while changing the type {}", targetType, excp); throw new WebScriptException("Change type failed!, targetType was: " + targetType, excp); } } /** * Gets the target type. * * @param payload the payload * @return the target type */ private QName getTargetType(final JSONObject payload) { try { final String type = payload.getString(TYPE_PARAM_KEY); QName qname; if (type.indexOf(String.valueOf(QName.NAMESPACE_BEGIN)) != -1) { qname = QName.createQName(type); } else { qname = QName.createQName(type, namespaceService); } return qname; } catch (JSONException jsonex) { throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Error occurred while extracting the target type from input json payload", jsonex); } } /** * Write response. * * @param response the response * @param changedType the changed type * @throws IOException the IO exception */ private void writeResponse(final WebScriptResponse response, final QName changedType) throws IOException { try { final JSONObject responsePayload = new JSONObject(); responsePayload.put(CURRENT_KEY, changedType.getPrefixString()); writeResponse(response, responsePayload, false, HttpStatus.SC_OK); } catch (JSONException jsonex) { throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Error occurred while writing json response for changedType: " + changedType, jsonex); } } /** * Gets the node ref. * * @param request the request * @return the node ref */ private NodeRef getNodeRef(final WebScriptRequest request) { final String storeType = getParam(request, STORE_TYPE); final String storeId = getParam(request, STORE_ID); final String identifier = getParam(request, PARAM_ID); return new NodeRef(storeType, storeId, identifier); } /** * Gets the param. * * @param request the request * @param paramName the param name * @return the param */ private String getParam(final WebScriptRequest request, final String paramName) { final String value = StringUtils.trimToNull(request.getServiceMatch().getTemplateVars().get(paramName)); if (StringUtils.isBlank(value)) { throw new WebScriptException(Status.STATUS_BAD_REQUEST, String.format("Value for param '%s' is missing, Please provide a valid input", paramName)); } return value; } /** * Write response. * @param response the response * @param jsonObject the json object * @param clearCache the clear cache * @param responseStatusCode the response status code * @throws IOException Signals that an I/O exception has occurred. */ private void writeResponse(final WebScriptResponse response, final JSONObject jsonObject, final boolean clearCache, final int responseStatusCode) throws IOException { final int length = jsonObject.toString().getBytes(ENCODING_UTF_8).length; response.setContentType(MimetypeMap.MIMETYPE_JSON); response.setContentEncoding(ENCODING_UTF_8); response.addHeader(CONTENT_LENGTH, String.valueOf(length)); if (clearCache) { response.addHeader(CACHE_CONTROL, NO_CACHE); //Calculate the expires date as per you need, i have kept a regular date for example purpose. response.addHeader(EXPIRES, "Thu, 04 Jan 2020 00:00:00 EDT"); response.addHeader(PRAGMA, NO_CACHE); } response.setStatus(responseStatusCode); response.getWriter().write(jsonObject.toString()); } }
- Add the following bean definition in *-context.xml (e.g. webscript-context.xml):
<bean id="webscript.org.alfresco.slingshot.documentlibrary.type.post" class="com.github.abhinavmishra14.webscript.ChangeTypeWebscript" parent="webscript"> <constructor-arg ref="NamespaceService" /> <constructor-arg ref="NodeService" /> </bean>
- To test whether change type is working or not, let's update the share-config-custom.xml file under
<config evaluator="string-compare" condition="DocumentLibrary"> to add type,sub-type config.Add following config:<config evaluator="string-compare" condition="DocumentLibrary"> <aspects> <visible/> <addable/> <removable/> </aspects> <types> <type name="cm:content"> <subtype name="demo:whitePaper"/> <subtype name="demo:supportingDoc"/> </type> <type name="demo:whitePaper"> <subtype name="demo:supportingDoc"/> </type> <type name="demo:supportingDoc"> <subtype name="demo:whitePaper"/> </type> </types> </config>
- Restart the repository and share. Select a file in your document library and click "Change Type" to use extended action.
Find a demo project here: https://github.com/abhinavmishra14/specialize-type-demo-----------------------------------------------------------------------------------------------------------------------------If you prefer to change the type via Repository JavaScript webscript, you can use the below given script as well:Assuming you know the nodeRef of the actionable node for which you want to change the type. You can find more info here
- Create "change-type.post.js" under "Repository> Data Dictionary> Web Scripts Extensions" folder.
function main(){ var nodeRefStr = args["nodeRef"]; //Node ref of the corrupted folder (An assumption for the script) var qNameToChangeType = args["qNameToChangeType"]; //Full qualified QName e.g.: {http://www.alfresco.org/model/content/1.0}folder try { var node=search.findNode(nodeRefStr); logger.log("NodeRef: "+ node.nodeRef+" | Name: "+node.name) var ctxt = Packages.org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext(); var nodeService = ctxt.getBean('NodeService', org.alfresco.service.cmr.repository.NodeService); var QName = Packages.org.alfresco.service.namespace.QName; var nodeTypeFolder = QName.createQName(qNameToChangeType); nodeService.setType(node.nodeRef, nodeTypeFolder); model.result = "Success!"; } catch (ex) { logger.log("Exception occurred: " + ex.message); model.result = ex.message; } } main();- Create "change-type.post.desc.xml" under "Repository> Data Dictionary> Web Scripts Extensions" folder.
<webscript> <shortname>Change Type</shortname> <description>Change type of a node <![CDATA[ Sample request: http://localhost:8080/alfresco/service/change-type?nodeRef=workspace://SpacesStore/e0df9384-9472-472e-95c0-2e091f452700&qNameToChangeType={http://www.alfresco.org/model/content/1.0}folder]]> </description> <url>/change-type?nodeRef={nodeRef}&qNameToChangeType={qNameToChangeType}</url> <authentication>admin</authentication> </webscript>
- Create "change-type.post.html.ftl" under "Repository> Data Dictionary> Web Scripts Extensions" folder.
<html> <head> <title>Change type result</title> </head> <body> <#if result??> ${result} </#if> </body> </html>-----------------------------------------------------------------------------------------------------------------------------If you prefer to change the type via Javascript console and want to change the type of multiple nodes in a given site,
you can use the below given script:var inputNodetype = "cm:content"; //Input content type or folder type. In this example it's cm:content and target is demo:whitePaper content type var siteShortName = "test"; //Site short name, Site name is 'Test', ShortName: 'test' var targetFullQualifiedNodetype = "{http://www.github.com/abhinavmishra14/model/demo/1.0}whitePaper"; //This must be fully qualified var folderPath = ""; //If you want to drill down search to a specific folder path. e.g. 'Assets/images' folder In documentLibrary var additionalQuery = ""; //for example, you can also pass any query like '=@cm\\:name:"*Test" (node name ends with Test)'. //This query is appended to final search query var skipCount = 0; var maxCount = 1000; var query = buildQuery(siteShortName, inputNodetype, folderPath, additionalQuery); var page = { skipCount: parseInt(skipCount), maxItems: parseInt(maxCount) }; var searchQuery = { query: query, language: "fts-alfresco", page: page }; logger.log("Executing SearchQuery: " + query); var foundNodes = search.query(searchQuery); logger.log("Total nodes found: " + foundNodes.length); for each(node in foundNodes) { var nodeName = node.properties.name; var nodeType = node.type; logger.log("NodeName: " + nodeName + " | NodeType: " + nodeType); if(node.typeShort == inputNodetype) { logger.log("Changing type for node: '"+nodeName+"'"); //Get the node service context var ctxt = Packages.org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext(); var nodeService = ctxt.getBean('NodeService', org.alfresco.service.cmr.repository.NodeService); var QName = Packages.org.alfresco.service.namespace.QName; var targetNodeType = QName.createQName(targetFullQualifiedNodetype); nodeService.setType(node.nodeRef, targetNodeType); logger.log("Change type to: '"+targetNodeType+"' has been completed."); } } function buildQuery(siteShortName, inputNodetype, folderPath, additionalQuery) { var query = 'PATH:"/app:company_home/st:sites/cm:' + siteShortName; if (!!folderPath) { //if not null then process query = query + '/cm:documentLibrary/'; var pathTokens = folderPath.split('/'); for (var each = 0; each < pathTokens.length; each++) { query = query + 'cm:' + search.ISO9075Encode(pathTokens[each].trim()) + '/'; } query = query + '/*"'; } else { query = query + '/cm:documentLibrary//*"'; } query = query + ' AND (TYPE:"'+inputNodetype+'")'; //Append additionalQuery query if any if ( !! additionalQuery) { //if not null then append query = query + ' AND ' + additionalQuery; } return query; }Sample Output:DEBUG - Executing SearchQuery: PATH:"/app:company_home/st:sites/cm:test/cm:documentLibrary//*" AND (TYPE:"cm:content") DEBUG - Total nodes found: 17 DEBUG - NodeName: xyz.jpeg | NodeType: {http://www.alfresco.org/model/content/1.0}content DEBUG - Changing type for node: 'xyz.jpeg' DEBUG - Change type to: '{http://www.github.com/abhinavmishra14/model/demo/1.0}whitePaper' has been completed. DEBUG - NodeName: abc.jpg | NodeType: {http://www.alfresco.org/model/content/1.0}content DEBUG - Changing type for node: 'abc.jpg' DEBUG - Change type to: '{http://www.github.com/abhinavmishra14/model/demo/1.0}whitePaper' has been completed. DEBUG - NodeName: download.png | NodeType: {http://www.alfresco.org/model/content/1.0}content DEBUG - Changing type for node: 'download.png' DEBUG - Change type to: '{http://www.github.com/abhinavmishra14/model/demo/1.0}whitePaper' has been completed.
Good morning
ReplyDeletei am trying to implement the action using OOTB action, but not working in docker implementation. where the file would be implemented
It is an extension of the repository webscript. You have to create the webscript by following the steps above and deploy into your docker image (amp or jar) and that should work. I would also recommend to go through this webscript tutorial : https://ecmarchitect.com/alfresco-developer-series-tutorials/webscripts/tutorial/tutorial.html
Delete