Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.0/Cloud)

85731: MNT-12461: Bring back the changes of MNT-11660
      - moved the new test from web-framework-commons to web-client, where it belongs (w-f-c shouldn't depend on remote-api)
      - renamed the test FormUIGetRestApiIT because it is an integration test, not a unit test
      - disabled the execution of this test. Needs more work to sort it out - probably missing dependencies
      85650: Reverse Merge HEAD-BUG-FIX (5.0/Cloud)
         << Breaks the build >>
         85615: Merged V4.2-BUG-FIX (4.2.4) to HEAD-BUG-FIX (5.0/Cloud)
            <<Reverting the reverse - unit test was fixed>>
            80815: Fix Build (pt2) Reverse Merge HEAD-BUG-FIX (5.0/Cloud)
               80357: Merged V4.2-BUG-FIX (4.2.4) to HEAD-BUG-FIX (5.0/Cloud)
                  79075: Merged V4.2.3 (4.2.3) to V4.2-BUG-FIX (4.2.4)
                     78918: Merged DEV to V4.2.3 (4.2.3)
                        78889: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                           Was added new unit test.
                  79079: Merged V4.2.3 (4.2.3) to V4.2-BUG-FIX (4.2.4)
                     78994: Merged DEV to PATCHES/V4.2.3 
                      78981 : MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                         Updated some code for unit test.
                  79081: Merged V4.2.3 (4.2.3) to V4.2-BUG-FIX (4.2.4)
                     79043: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                        Added webframeworkcommons classpath for unit test in build.xml .
                  79138: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Rename config file for test and update unit test source.
                  79140: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Deleted old config file for test.
                  79283: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Added info messages to unit test
                  80123: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Added info messages and log to unit test. 
                  80207: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Updated the freemarker template for test webscript
                  80261: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Updated unit test. 
                  80302: MNT-11660 : Share textarea.ftl appears even when the aspect of the property is not applied
                     Updated unit test. 
         


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@94515 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2015-01-31 09:33:48 +00:00
parent 571f2feb1d
commit 381840c2bd
7 changed files with 635 additions and 3 deletions

20
pom.xml
View File

@@ -151,6 +151,20 @@
<classifier>tests</classifier> <classifier>tests</classifier>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-remote-api</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.extensions.surf</groupId>
<artifactId>spring-webscripts</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
@@ -428,9 +442,9 @@
</goals> </goals>
<configuration> <configuration>
<includes> <includes>
<include>**/org/alfresco/web/app/servlet/DefaultRemoteUserMapperTest.java</include> <!--
<include>**/org/alfresco/web/app/servlet/KerberosRemoteUserMapperTest.java</include> <include>**/FormUIGetRestApiIT.java</include>
<include>**/org/alfresco/web/forms/FormsTest.java</include> -->
</includes> </includes>
</configuration> </configuration>
</execution> </execution>

View File

@@ -0,0 +1,389 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.web.scripts.forms;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.web.config.forms.FormConfigElement;
import org.alfresco.web.config.forms.FormField;
import org.alfresco.web.config.forms.FormsConfigElement;
import org.alfresco.web.config.forms.NodeTypeEvaluator;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.extensions.config.Config;
import org.springframework.extensions.config.ConfigSource;
import org.springframework.extensions.config.source.ClassPathConfigSource;
import org.springframework.extensions.config.xml.XMLConfigService;
import org.springframework.extensions.surf.exception.ConnectorServiceException;
import org.springframework.extensions.webscripts.TestWebScriptServer;
import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest;
import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest;
import org.springframework.extensions.webscripts.TestWebScriptServer.Response;
import org.springframework.extensions.webscripts.connector.ResponseStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONTokener;
import org.alfresco.repo.web.scripts.servlet.LocalTestRunAsAuthenticatorFactory;
public class FormUIGetRestApiIT extends TestCase
{
private static final Log log = LogFactory.getLog(FormUIGetRestApiIT.class);
private final static String[] CONFIG_LOCATIONS = new String[] { "classpath:alfresco/application-context.xml", "classpath:alfresco/web-scripts-application-context.xml",
"classpath:alfresco/web-scripts-application-context-webframework-test.xml" };
private static ClassPathXmlApplicationContext ctx = (ClassPathXmlApplicationContext) ApplicationContextHelper.getApplicationContext(CONFIG_LOCATIONS);
private static TestWebScriptServer server = (TestWebScriptServer) ctx.getBean("webscripts.web.framework.test");
protected NodeService nodeService;
protected FileFolderService fileFolderService;
protected Repository repositoryHelper;
protected NodeRef containerNodeRef;
protected TransactionService transactionService;
private NodeRef folderWithoutAspect = null;
private NodeRef folderWithAspect = null;
@Override
public void setUp() throws Exception
{
super.setUp();
this.fileFolderService = (FileFolderService) ctx.getBean("FileFolderService");
this.repositoryHelper = (Repository) ctx.getBean("repositoryHelper");
this.nodeService = (NodeService) ctx.getBean("NodeService");
this.transactionService = (TransactionService) ctx.getBean("transactionService");
server.setServletAuthenticatorFactory(new LocalTestRunAsAuthenticatorFactory());
NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome();
String guid = GUID.generate();
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
folderWithoutAspect = fileFolderService.create(companyHomeNodeRef, "folder_" + guid, ContentModel.TYPE_FOLDER).getNodeRef();
assertNotNull("Doesn't create folder", folderWithoutAspect);
folderWithAspect = fileFolderService.create(companyHomeNodeRef, "folder_aspect_" + guid, ContentModel.TYPE_FOLDER).getNodeRef();
assertNotNull("Doesn't create folder", folderWithoutAspect);
// add 'dublincore' aspect
Map<QName, Serializable> aspectProps = new HashMap<QName, Serializable>(1);
aspectProps.put(ContentModel.PROP_SUBJECT, "Test subject");
nodeService.addAspect(folderWithAspect, ContentModel.ASPECT_DUBLINCORE, aspectProps);
}
@Override
public void tearDown()
{
if (folderWithoutAspect != null && fileFolderService.exists(folderWithoutAspect))
{
fileFolderService.delete(folderWithoutAspect);
}
if (folderWithAspect != null && fileFolderService.exists(folderWithAspect))
{
fileFolderService.delete(folderWithAspect);
}
}
public void testMNT11660() throws Exception
{
FormUIGet formUIGet = (FormUIGetExtend) ctx.getBean("webscript.org.alfresco.test.components.form.form.get");
assertNotNull("'FormUIGetExtend' bean for test is null.", formUIGet);
ConfigSource configSource = new ClassPathConfigSource("test-config-custom-forms.xml");
XMLConfigService svc = new XMLConfigService(configSource);
svc.initConfig();
formUIGet.setConfigService(svc);
GetRequest requestWithAspect = new GetRequest("/test/components/form?htmlid=template_default-formContainer&itemKind=node&itemId=" + folderWithAspect.toString()
+ "&formId=null&mode=view");
Response rspFormWithAspect = server.submitRequest(requestWithAspect.getMethod(), requestWithAspect.getFullUri(), requestWithAspect.getHeaders(),
requestWithAspect.getBody(), requestWithAspect.getEncoding(), requestWithAspect.getType());
assertEquals("The status of response is " + rspFormWithAspect.getStatus(), 200, rspFormWithAspect.getStatus());
String contentWithAspect = rspFormWithAspect.getContentAsString();
log.info("Response form for node with dublincore aspect status is " + rspFormWithAspect.getStatus() + " content is " + contentWithAspect);
assertNotNull("Response content for 'contentWithAspect' is null", contentWithAspect);
assertTrue("Return the following content: " + contentWithAspect, contentWithAspect.contains("My Set"));
GetRequest requestWithoutAspect = new GetRequest("/test/components/form?htmlid=template_default-formContainer&itemKind=node&itemId=" + folderWithoutAspect.toString()
+ "&formId=null&mode=view");
Response rspFormWithoutAspect = server.submitRequest(requestWithoutAspect.getMethod(), requestWithoutAspect.getFullUri(), requestWithoutAspect.getHeaders(),
requestWithoutAspect.getBody(), requestWithoutAspect.getEncoding(), requestWithoutAspect.getType());
assertEquals("The status of response is " + rspFormWithoutAspect.getStatus(), 200, rspFormWithoutAspect.getStatus());
String contentWithoutAspect = rspFormWithoutAspect.getContentAsString();
log.info("Response form for node without aspect status is " + rspFormWithoutAspect.getStatus() + " content is " + contentWithoutAspect);
assertNotNull("Response content for 'contentWithoutAspect' is null", contentWithoutAspect);
assertFalse("Return the following content: " + contentWithoutAspect, contentWithoutAspect.contains("My Set"));
}
private static class FormUIGetExtend extends FormUIGet
{
@Override
protected FormConfigElement getFormConfig(String itemId, String formId)
{
FormConfigElement formConfig = null;
Config configResult = this.configService.getConfig(itemId);
FormsConfigElement formsConfig = (FormsConfigElement) configResult.getConfigElement(CONFIG_FORMS);
assertNotNull("The ConfigElement object doesn't exist", formsConfig);
if (formsConfig != null)
{
// Extract the form we are looking for
if (formsConfig != null)
{
// try and retrieve the specified form
if (formId != null && formId.length() > 0)
{
formConfig = formsConfig.getForm(formId);
}
// fall back to the default form
if (formConfig == null)
{
formConfig = formsConfig.getDefaultForm();
}
}
}
assertNotNull("The ConfigElement object doesn't exist", formConfig);
return formConfig;
}
private String getStringFromInputStream(InputStream is)
{
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try
{
br = new BufferedReader(new InputStreamReader(is));
while ((line = br.readLine()) != null)
{
sb.append(line);
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (br != null)
{
try
{
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
assertTrue("StringBuilder has 0 length", sb.length() > 0);
return sb.toString();
}
@Override
protected org.springframework.extensions.webscripts.connector.Response retrieveFormDefinition(String itemKind, String itemId, List<String> visibleFields,
FormConfigElement formConfig)
{
org.springframework.extensions.webscripts.connector.Response response = null;
try
{
assertEquals("Parameter 'itemKind' isn't 'node' value", "node", itemKind);
assertFalse("itemId is empty", itemId != null && itemId.isEmpty());
assertTrue("Visible fields are empty", visibleFields.size() > 0);
assertEquals("Form config 'name' field isn't 'form' value", "form", formConfig.getName());
ByteArrayInputStream bais = generateFormDefPostBody(itemKind, itemId, visibleFields, formConfig);
assertTrue("Can't read bytes from ByteArrayInputStream ", bais.available() > 0);
String json = getStringFromInputStream(bais);
log.info("Request 'formdefinitions' json is: " + json);
PostRequest request = new PostRequest("/api/formdefinitions", json, "application/json");
org.springframework.extensions.webscripts.TestWebScriptServer.Response responseTest = server.submitRequest(request.getMethod(), request.getFullUri(),
request.getHeaders(), request.getBody(), request.getEncoding(), request.getType());
if (responseTest.getStatus() == 200)
{
JSONObject jsonParsedObject = new JSONObject(new JSONTokener(responseTest.getContentAsString()));
assertNotNull("JSON from responseTest is null", jsonParsedObject);
Object dataObj = jsonParsedObject.get("data");
assertEquals(JSONObject.class, dataObj.getClass());
JSONObject rootDataObject = (JSONObject)dataObj;
String item = (String)rootDataObject.get("item");
String submissionUrl = (String)rootDataObject.get("submissionUrl");
String type = (String)rootDataObject.get("type");
JSONObject definitionObject = (JSONObject)rootDataObject.get("definition");
JSONObject formDataObject = (JSONObject)rootDataObject.get("formData");
assertNotNull("Item is null ", item);
assertNotNull("Submission url is null ", submissionUrl);
assertNotNull("Type is null ", type);
assertNotNull("Definition is null ", definitionObject);
assertNotNull("Form data is null ", formDataObject);
log.info("Response form 'formdefinitions' json 'data' is: " + dataObj);
ResponseStatus status = new ResponseStatus();
status.setCode(responseTest.getStatus());
assertFalse("Response content is empty", responseTest.getContentAsString().isEmpty());
response = new org.springframework.extensions.webscripts.connector.Response(responseTest.getContentAsString(), status);
assertNotNull("Response data is null.", response.getText());
}
else
{
assertEquals("Response /api/formdefinitions is not 200 status", 200, responseTest.getStatus());
}
}
catch (Exception e)
{
log.error("Response form 'formdefinitions' exception : " + e.getMessage());
}
return response;
}
@Override
protected Field generateFieldModel(ModelContext context, String fieldName, FormField fieldConfig)
{
Field field = null;
try
{
// make sure the field is not ambiguous
if (isFieldAmbiguous(context, fieldName))
{
field = generateTransientFieldModel(fieldName, "/org/alfresco/components/form/controls/ambiguous.ftl");
}
else
{
JSONObject fieldDefinition = discoverFieldDefinition(context, fieldName);
if (fieldDefinition != null)
{
// create the initial field model
field = new Field();
// populate the model with the appropriate data
processFieldIdentification(context, field, fieldDefinition, fieldConfig);
processFieldState(context, field, fieldDefinition, fieldConfig);
processFieldText(context, field, fieldDefinition, fieldConfig);
processFieldData(context, field, fieldDefinition, fieldConfig);
processFieldContent(context, field, fieldDefinition, fieldConfig);
}
else
{
// the field does not have a definition but may be a 'transient' field
field = generateTransientFieldModel(context, fieldName, fieldDefinition, fieldConfig);
}
}
}
catch (JSONException je)
{
field = null;
log.error("Generate field model exception: " + je.getMessage());
}
log.info("Generated field model " + fieldName + " is null");
return field;
}
}
public static class NodeTypeEvaluatorExtend extends NodeTypeEvaluator
{
protected String callMetadataService(String nodeString) throws ConnectorServiceException
{
GetRequest request = new GetRequest("/api/metadata?nodeRef=" + nodeString + "&shortQNames=true");
Response response = null;
String jsonResponse = null;
try
{
response = server.submitRequest(request.getMethod(), request.getFullUri(), request.getHeaders(), request.getBody(), request.getEncoding(), request.getType());
if (response != null)
{
jsonResponse = response.getContentAsString();
}
}
catch (UnsupportedEncodingException e)
{
throw new AlfrescoRuntimeException(e.getMessage());
}
catch (IOException e)
{
throw new AlfrescoRuntimeException(e.getMessage());
}
assertNotNull("Response /api/metadata is null", jsonResponse);
log.info("Response /api/metadata json is: " + jsonResponse);
return jsonResponse;
}
}
}

View File

@@ -0,0 +1,5 @@
<webscript>
<shortname>Form Component</shortname>
<description>Component that renders a form based on a repository form model</description>
<url>/test/components/form</url>
</webscript>

View File

@@ -0,0 +1,23 @@
<#import "form.lib.ftl" as formLib />
<#if error?exists>
<div class="error">${error}</div>
<#elseif form?exists>
<#assign formId=args.htmlid?js_string?html + "-form">
<#assign formUI><#if args.formUI??>${args.formUI}<#else>true</#if></#assign>
<@formLib.renderFormContainer formId=formId>
<#list form.structure as item>
<#if item.kind == "set">
<#if item.children?size &gt; 0>
<@formLib.renderSet set=item />
</#if>
<#else>
<@formLib.renderField field=form.fields[item.id] />
</#if>
</#list>
</@>
<#else>
<div class="form-container">Form doesn't exist</div>
</#if>

View File

@@ -0,0 +1,92 @@
<#macro renderFormContainer formId>
<div id="${formId}-container" class="form-container">
<#if form.showCaption?? && form.showCaption>
<div id="${formId}-caption" class="caption"><span class="mandatory-indicator">*</span>${msg("form.required.fields")}</div>
</#if>
<#if form.mode != "view">
<form id="${formId}" method="${form.method}" accept-charset="utf-8" enctype="${form.enctype}" action="${form.submissionUrl?html}">
</#if>
<#if form.mode == "create" && form.destination?? && form.destination?length &gt; 0>
<input id="${formId}-destination" name="alf_destination" type="hidden" value="${form.destination?html}" />
</#if>
<#if form.mode != "view" && form.redirect?? && form.redirect?length &gt; 0>
<input id="${formId}-redirect" name="alf_redirect" type="hidden" value="${form.redirect?html}" />
</#if>
<div id="${formId}-fields" class="form-fields">
<#nested>
</div>
<#if form.mode != "view">
<@renderFormButtons formId=formId />
</form>
</#if>
</div>
</#macro>
<#macro renderFormButtons formId>
<div id="${formId}-buttons" class="form-buttons">
<#if form.showSubmitButton?? && form.showSubmitButton>
<input id="${formId}-submit" type="submit" value="${msg("form.button.submit.label")}" />&nbsp;
</#if>
<#if form.showResetButton?? && form.showResetButton>
<input id="${formId}-reset" type="reset" value="${msg("form.button.reset.label")}" />&nbsp;
</#if>
<#if form.showCancelButton?? && form.showCancelButton>
<input id="${formId}-cancel" type="button" value="${msg("form.button.cancel.label")}" />
</#if>
</div>
</#macro>
<#macro renderField field>
<#if field.control?? && field.control.template??>
<#assign fieldHtmlId=args.htmlid?html + "_" + field.id >
<#include "${field.control.template}" />
</#if>
</#macro>
<#macro renderSet set>
<div class="set">
<#if set.appearance??>
<#if set.appearance == "fieldset">
<fieldset><legend>${set.label}</legend>
<#elseif set.appearance == "bordered-panel">
<div class="set-bordered-panel">
<div class="set-bordered-panel-heading">${set.label}</div>
<div class="set-bordered-panel-body">
<#elseif set.appearance == "panel">
<div class="set-panel">
<div class="set-panel-heading">${set.label}</div>
<div class="set-panel-body">
<#elseif set.appearance == "title">
<div class="set-title">${set.label}</div>
<#elseif set.appearance == "whitespace">
<div class="set-whitespace"></div>
</#if>
</#if>
<#if set.template??>
<#include "${set.template}" />
<#else>
<#list set.children as item>
<#if item.kind == "set">
<@renderSet set=item />
<#else>
<@renderField field=form.fields[item.id] />
</#if>
</#list>
</#if>
<#if set.appearance??>
<#if set.appearance == "fieldset">
</fieldset>
<#elseif set.appearance == "panel" || set.appearance == "bordered-panel">
</div>
</div>
</#if>
</#if>
</div>
</#macro>

View File

@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- -->
<!-- API Testing -->
<!-- -->
<bean id="webframework.store.classpath.abstract" class="org.springframework.extensions.webscripts.ClassPathStore" abstract="true" init-method="init" />
<bean id="webscripts.store.client.extension" parent="webframework.store.classpath.abstract">
<property name="mustExist"><value>true</value></property>
<property name="classPath"><value>alfresco/site-webscripts</value></property>
</bean>
<bean id="webscripts.web.framework.test" class="org.springframework.extensions.webscripts.TestWebScriptServer">
<property name="container" ref="webscripts.container" />
<property name="configService" ref="web.config" />
</bean>
<bean id="webscript.org.alfresco.test.components.form.form.get" class="org.alfresco.web.scripts.forms.FormUIGetRestApiTest.FormUIGetExtend" parent="webscript">
<property name="configService" ref="web.config" />
</bean>
</beans>

View File

@@ -0,0 +1,83 @@
<alfresco-config>
<plug-ins>
<element-readers>
<element-reader element-name="forms" class="org.alfresco.web.config.forms.FormsElementReader"/>
</element-readers>
<evaluators>
<evaluator id="node-type" class="org.alfresco.web.scripts.forms.FormUIGetRestApiTest$NodeTypeEvaluatorExtend" />
</evaluators>
</plug-ins>
<!-- cm:folder type (existing nodes) -->
<config evaluator="node-type" condition="cm:folder" replace="true">
<forms>
<!-- Default form configuration for the cm:folder type -->
<form>
<field-visibility>
<show id="cm:name" />
<show id="cm:title" force="true" />
<show id="cm:description" force="true" />
<!-- cm:dublincore aspect -->
<show id="cm:publisher"/>
<show id="cm:contributor"/>
<show id="cm:type"/>
<show id="cm:identifier"/>
<show id="cm:dcsource"/>
<show id="cm:coverage"/>
<show id="cm:rights"/>
<show id="cm:subject"/>
<!-- tags and categories -->
<show id="cm:taggable" for-mode="edit" force="true" />
<show id="cm:categories" />
<!-- emailserver:aliasable aspect -->
<show id="emailserver:alias" />
</field-visibility>
<appearance>
<field id="cm:name">
<control>
<control-param name="maxLength">255</control-param>
</control>
</field>
<field id="cm:title">
<control template="/org/alfresco/components/form/controls/textfield.ftl" />
</field>
<field id="cm:description">
<control>
<control-param name="activateLinks">true</control-param>
</control>
</field>
<field id="cm:taggable">
<control>
<control-param name="compactMode">true</control-param>
<control-param name="params">aspect=cm:taggable</control-param>
<control-param name="createNewItemUri">/api/tag/workspace/SpacesStore</control-param>
<control-param name="createNewItemIcon">tag</control-param>
</control>
</field>
<field id="cm:categories">
<control>
<control-param name="compactMode">true</control-param>
</control>
</field>
<set id="mySet" appearance="title" label="My Set" />
<field id="cm:subject" set="mySet">
<control template="/org/alfresco/components/form/controls/textarea.ftl">
<control-param name="rows">10</control-param>
</control>
</field>
<field id="cm:publisher" set="mySet">
<control>
<control-param name="maxLength">255</control-param>
</control>
</field>
</appearance>
</form>
</forms>
</config>
</alfresco-config>