mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged DEV/FORMS to HEAD (all activity from branch creation r12855 through r13056)
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13058 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Form</shortname>
|
||||||
|
<description>Get a form to view or edit the metadata of a given node.</description>
|
||||||
|
<url>/api/forms/node/{store_type}/{store_id}/{id}</url>
|
||||||
|
<url>/api/forms/node/{path}</url>
|
||||||
|
<format default="json"/>
|
||||||
|
<authentication>user</authentication>
|
||||||
|
<transaction>required</transaction>
|
||||||
|
</webscript>
|
@@ -0,0 +1,104 @@
|
|||||||
|
function main()
|
||||||
|
{
|
||||||
|
// Extract template args
|
||||||
|
var ta_storeType = url.templateArgs['store_type'];
|
||||||
|
var ta_storeId = url.templateArgs['store_id'];
|
||||||
|
var ta_id = url.templateArgs['id'];
|
||||||
|
var ta_path = url.templateArgs['path'];
|
||||||
|
|
||||||
|
logger.log("ta_storeType = " + ta_storeType);
|
||||||
|
logger.log("ta_storeId = " + ta_storeId);
|
||||||
|
logger.log("ta_id = " + ta_id);
|
||||||
|
logger.log("ta_path = " + ta_path);
|
||||||
|
|
||||||
|
var formUrl = '';
|
||||||
|
// The template argument 'path' only appears in the second URI template.
|
||||||
|
if (ta_path != null)
|
||||||
|
{
|
||||||
|
//TODO Need to test this path.
|
||||||
|
formUrl = ta_path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
formUrl = ta_storeType + '://' + ta_storeId + '/' + ta_id;
|
||||||
|
}
|
||||||
|
logger.log("formUrl = " + formUrl);
|
||||||
|
|
||||||
|
var formScriptObj = formService.getForm(formUrl);
|
||||||
|
|
||||||
|
if (formScriptObj == null)
|
||||||
|
{
|
||||||
|
var message = "Form " + formUrl + " not found.";
|
||||||
|
logger.log(message);
|
||||||
|
status.setCode(404, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formModel = {};
|
||||||
|
formModel.data = {};
|
||||||
|
|
||||||
|
formModel.data.item = '/api/node/' + ta_storeType + '/' + ta_storeId + '/' + ta_id;
|
||||||
|
formModel.data.submissionUrl = '/api/forms/node/' + ta_storeType + '/' + ta_storeId + '/' + ta_id;
|
||||||
|
formModel.data.type = formScriptObj.type;
|
||||||
|
|
||||||
|
formModel.data.definition = {};
|
||||||
|
formModel.data.definition.fields = {};
|
||||||
|
for (var fieldName in formScriptObj.fieldDefinitionData)
|
||||||
|
{
|
||||||
|
// We're explicitly listing the object fields of FieldDefinition.java and its
|
||||||
|
// subclasses here.
|
||||||
|
// I don't see a way to get these dynamically at runtime.
|
||||||
|
var supportedBaseFieldNames = ['name', 'label', 'description', 'binding',
|
||||||
|
'defaultValue', 'group', 'protectedField'];
|
||||||
|
var supportedPropertyFieldNames = ['dataType', 'mandatory',
|
||||||
|
'repeats', 'constraints'];
|
||||||
|
var supportedAssociationFieldNames = ['endpointType', 'endpointDirection',
|
||||||
|
'endpointMandatory', 'endpointMany'];
|
||||||
|
|
||||||
|
var allSupportedFieldNames = supportedBaseFieldNames
|
||||||
|
.concat(supportedPropertyFieldNames)
|
||||||
|
.concat(supportedAssociationFieldNames);
|
||||||
|
|
||||||
|
formModel.data.definition.fields[fieldName] = {};
|
||||||
|
for (var i = 0; i < allSupportedFieldNames.length; i++) {
|
||||||
|
var nextSupportedName = allSupportedFieldNames[i];
|
||||||
|
var nextValue = formScriptObj.fieldDefinitionData[fieldName][nextSupportedName];
|
||||||
|
|
||||||
|
if (nextValue != null) {
|
||||||
|
formModel.data.definition.fields[fieldName][nextSupportedName] = nextValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for the 'type' property
|
||||||
|
// For now, this can have a value of 'property' or 'association'
|
||||||
|
|
||||||
|
//TODO Temporary impl here.
|
||||||
|
if (formModel.data.definition.fields[fieldName]['dataType'] != null)
|
||||||
|
{
|
||||||
|
formModel.data.definition.fields[fieldName]['type'] = 'property';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
formModel.data.definition.fields[fieldName]['type'] = 'association';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formModel.data.formData = {};
|
||||||
|
for (var k in formScriptObj.formData.data)
|
||||||
|
{
|
||||||
|
var value = formScriptObj.formData.data[k].value;
|
||||||
|
|
||||||
|
if (value instanceof java.util.Date)
|
||||||
|
{
|
||||||
|
formModel.data.formData[k] = utils.toISO8601(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
formModel.data.formData[k] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.form = formModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@@ -0,0 +1,2 @@
|
|||||||
|
<#import "form.lib.ftl" as formLib/>
|
||||||
|
<@formLib.formJSON form=form/>
|
@@ -0,0 +1,62 @@
|
|||||||
|
<#macro formJSON form>
|
||||||
|
<#escape x as jsonUtils.encodeJSONString(x)>
|
||||||
|
{
|
||||||
|
"data" :
|
||||||
|
{
|
||||||
|
"item" : "${form.data.item}",
|
||||||
|
"submissionUrl" : "${form.data.submissionUrl}",
|
||||||
|
"type" : "${form.data.type}",
|
||||||
|
"definition" :
|
||||||
|
{
|
||||||
|
"fields" :
|
||||||
|
[
|
||||||
|
<#list form.data.definition.fields?keys as k>
|
||||||
|
{
|
||||||
|
<#list form.data.definition.fields[k]?keys as c>
|
||||||
|
<#if form.data.definition.fields[k][c]?is_boolean>
|
||||||
|
"${c}" : ${form.data.definition.fields[k][c]?string}<#if c_has_next>,</#if>
|
||||||
|
<#elseif form.data.definition.fields[k][c]?is_sequence>
|
||||||
|
"${c}" :
|
||||||
|
[{
|
||||||
|
<#list form.data.definition.fields[k][c] as q>
|
||||||
|
"type" : "${q.type}"<#if q.params?exists>,
|
||||||
|
"params" : {
|
||||||
|
<#list q.params?keys as p>
|
||||||
|
<#-- Render booleans without the inverted commas -->
|
||||||
|
|
||||||
|
<#-- Can I create a macro for boolean rendering? -->
|
||||||
|
|
||||||
|
<#if q.params[p]?is_boolean>
|
||||||
|
"${p}" : ${q.params[p]}<#if p_has_next>,</#if>
|
||||||
|
<#else>
|
||||||
|
"${p}" : "${q.params[p]}"<#if p_has_next>,</#if>
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
}
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
}]<#if c_has_next>,</#if>
|
||||||
|
<#else>
|
||||||
|
"${c}" : "${form.data.definition.fields[k][c]}"<#if c_has_next>,</#if>
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
}<#if k_has_next>,</#if>
|
||||||
|
</#list>
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"formData" :
|
||||||
|
{
|
||||||
|
<#list form.data.formData?keys as k>
|
||||||
|
<#if form.data.formData[k]?is_boolean>
|
||||||
|
<#-- Render boolean data without the surrounding inverted commas -->
|
||||||
|
"${k}" : ${form.data.formData[k]?string}<#if k_has_next>,</#if>
|
||||||
|
<#else>
|
||||||
|
<#-- All other data rendered with inverted commas -->
|
||||||
|
"${k}" : "${form.data.formData[k]}"<#if k_has_next>,</#if>
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</#escape>
|
||||||
|
</#macro>
|
@@ -0,0 +1,8 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Form</shortname>
|
||||||
|
<description>Handles the submission of a form</description>
|
||||||
|
<url>/api/forms/node/{store_type}/{store_id}/{id}</url>
|
||||||
|
<url>/api/forms/node/{path}</url>
|
||||||
|
<authentication>user</authentication>
|
||||||
|
<transaction>required</transaction>
|
||||||
|
</webscript>
|
@@ -0,0 +1,15 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Form Posted</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>${message}</h2>
|
||||||
|
<#if data.fields?exists>
|
||||||
|
<ul>
|
||||||
|
<#list data.fields as field>
|
||||||
|
<li>${field.name} = ${field.value}</li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
</#if>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -0,0 +1,43 @@
|
|||||||
|
function main()
|
||||||
|
{
|
||||||
|
var ta_storeType = url.templateArgs['store_type'];
|
||||||
|
var ta_storeId = url.templateArgs['store_id'];
|
||||||
|
var ta_id = url.templateArgs['id'];
|
||||||
|
var ta_mode = url.templateArgs['mode'];
|
||||||
|
var ta_path = url.templateArgs['path'];
|
||||||
|
|
||||||
|
var nodeRef = '';
|
||||||
|
// The template argument 'path' only appears in the second URI template.
|
||||||
|
if (ta_path != null)
|
||||||
|
{
|
||||||
|
//TODO Need to test this path.
|
||||||
|
nodeRef = ta_path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeRef = ta_storeType + '://' + ta_storeId + '/' + ta_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("POST request received for nodeRef: " + nodeRef);
|
||||||
|
|
||||||
|
// TODO: check the given nodeRef is real
|
||||||
|
|
||||||
|
// persist the submitted data using the most appropriate data set
|
||||||
|
if (typeof formdata !== "undefined")
|
||||||
|
{
|
||||||
|
model.data = formdata;
|
||||||
|
formService.saveForm(nodeRef, formdata);
|
||||||
|
}
|
||||||
|
else if (typeof json !== "undefined")
|
||||||
|
{
|
||||||
|
formService.saveForm(nodeRef, json);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
formService.saveForm(nodeRef, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.message = "Successfully updated node " + nodeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@@ -0,0 +1,202 @@
|
|||||||
|
package org.alfresco.repo.web.scripts.forms;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
|
import org.alfresco.repo.model.Repository;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
|
import org.alfresco.repo.web.scripts.BaseWebScriptTest;
|
||||||
|
import org.alfresco.repo.web.scripts.thumbnail.ThumbnailServiceTest;
|
||||||
|
import org.alfresco.service.cmr.model.FileFolderService;
|
||||||
|
import org.alfresco.service.cmr.model.FileInfo;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentService;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.alfresco.util.GUID;
|
||||||
|
import org.alfresco.web.scripts.TestWebScriptServer.GetRequest;
|
||||||
|
import org.alfresco.web.scripts.TestWebScriptServer.Response;
|
||||||
|
import org.alfresco.web.scripts.json.JSONUtils;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
|
public class TestFormRestAPI extends BaseWebScriptTest {
|
||||||
|
private FileFolderService fileFolderService;
|
||||||
|
private ContentService contentService;
|
||||||
|
private NodeService nodeService;
|
||||||
|
private Repository repositoryHelper;
|
||||||
|
private Response response;
|
||||||
|
private String jsonResponseString;
|
||||||
|
private NodeRef testRoot;
|
||||||
|
private NodeRef testPdfNode;
|
||||||
|
private String pathToTestPdfNode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception
|
||||||
|
{
|
||||||
|
super.setUp();
|
||||||
|
this.fileFolderService = (FileFolderService)getServer().getApplicationContext().getBean("FileFolderService");
|
||||||
|
this.contentService = (ContentService)getServer().getApplicationContext().getBean("ContentService");
|
||||||
|
this.repositoryHelper = (Repository)getServer().getApplicationContext().getBean("repositoryHelper");
|
||||||
|
this.nodeService = (NodeService)getServer().getApplicationContext().getBean("NodeService");
|
||||||
|
|
||||||
|
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
|
||||||
|
|
||||||
|
this.testRoot = this.repositoryHelper.getCompanyHome();
|
||||||
|
|
||||||
|
// Create a dummy node purely for test purposes.
|
||||||
|
InputStream pdfStream = ThumbnailServiceTest.class.getClassLoader().getResourceAsStream("org/alfresco/repo/web/scripts/forms/test_doc.pdf");
|
||||||
|
assertNotNull(pdfStream);
|
||||||
|
|
||||||
|
String guid = GUID.generate();
|
||||||
|
|
||||||
|
FileInfo fileInfoPdf = this.fileFolderService.create(this.testRoot, "test_forms_doc" + guid + ".pdf", ContentModel.TYPE_CONTENT);
|
||||||
|
this.testPdfNode = fileInfoPdf.getNodeRef();
|
||||||
|
|
||||||
|
// Add an aspect.
|
||||||
|
Map<QName, Serializable> aspectProps = new HashMap<QName, Serializable>(2);
|
||||||
|
aspectProps.put(ContentModel.PROP_TITLE, "Test form title");
|
||||||
|
aspectProps.put(ContentModel.PROP_DESCRIPTION, "Test form description");
|
||||||
|
nodeService.addAspect(testPdfNode, ContentModel.ASPECT_TITLED, aspectProps);
|
||||||
|
|
||||||
|
ContentWriter contentWriter = this.contentService.getWriter(fileInfoPdf.getNodeRef(), ContentModel.PROP_CONTENT, true);
|
||||||
|
contentWriter.setEncoding("UTF-8");
|
||||||
|
contentWriter.setMimetype(MimetypeMap.MIMETYPE_PDF);
|
||||||
|
contentWriter.putContent(pdfStream);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("/api/forms/node/workspace/")
|
||||||
|
.append(testPdfNode.getStoreRef().getIdentifier())
|
||||||
|
.append("/")
|
||||||
|
.append(testPdfNode.getId());
|
||||||
|
this.pathToTestPdfNode = builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Add a tearDown which deletes the temporary pdf file above.
|
||||||
|
|
||||||
|
public void testResponseContentType() throws Exception
|
||||||
|
{
|
||||||
|
sendGetReqAndInitRspData(pathToTestPdfNode, 200);
|
||||||
|
assertEquals("application/json;charset=UTF-8", response.getContentType());
|
||||||
|
|
||||||
|
//TODO Remove this.
|
||||||
|
System.out.println(jsonResponseString);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Perhaps separate positive and negative test cases into two JUnit classes.
|
||||||
|
public void testGetFormForNonExistentNode() throws Exception
|
||||||
|
{
|
||||||
|
sendGetReqAndInitRspData(pathToTestPdfNode.replaceAll("\\d", "x"), 404);
|
||||||
|
assertEquals("application/json;charset=UTF-8", response.getContentType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testJsonContentParsesCorrectly() throws Exception
|
||||||
|
{
|
||||||
|
sendGetReqAndInitRspData(pathToTestPdfNode, 200);
|
||||||
|
|
||||||
|
Object jsonObject = new JSONUtils().toObject(jsonResponseString);
|
||||||
|
assertNotNull("JSON object was null.", jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testJsonUpperStructure() throws Exception
|
||||||
|
{
|
||||||
|
sendGetReqAndInitRspData(pathToTestPdfNode, 200);
|
||||||
|
|
||||||
|
JSONObject jsonParsedObject = new JSONObject(new JSONTokener(jsonResponseString));
|
||||||
|
assertNotNull(jsonParsedObject);
|
||||||
|
|
||||||
|
Object dataObj = jsonParsedObject.get("data");
|
||||||
|
assertEquals(JSONObject.class, dataObj.getClass());
|
||||||
|
JSONObject rootDataObject = (JSONObject)dataObj;
|
||||||
|
|
||||||
|
assertEquals(5, rootDataObject.length());
|
||||||
|
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);
|
||||||
|
assertNotNull(submissionUrl);
|
||||||
|
assertNotNull(type);
|
||||||
|
assertNotNull(definitionObject);
|
||||||
|
assertNotNull(formDataObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testJsonFormData() throws Exception
|
||||||
|
{
|
||||||
|
sendGetReqAndInitRspData(pathToTestPdfNode, 200);
|
||||||
|
|
||||||
|
JSONObject jsonParsedObject = new JSONObject(new JSONTokener(jsonResponseString));
|
||||||
|
assertNotNull(jsonParsedObject);
|
||||||
|
|
||||||
|
JSONObject rootDataObject = (JSONObject)jsonParsedObject.get("data");
|
||||||
|
|
||||||
|
JSONObject formDataObject = (JSONObject)rootDataObject.get("formData");
|
||||||
|
List<String> keys = new ArrayList<String>();
|
||||||
|
for (Iterator iter = formDataObject.keys(); iter.hasNext(); )
|
||||||
|
{
|
||||||
|
keys.add((String)iter.next());
|
||||||
|
}
|
||||||
|
// Threshold is a rather arbitrary number. I simply want to ensure that there
|
||||||
|
// are *some* entries in the formData hash.
|
||||||
|
final int threshold = 5;
|
||||||
|
int actualKeyCount = keys.size();
|
||||||
|
assertTrue("Expected more than " + threshold +
|
||||||
|
" entries in formData. Actual: " + actualKeyCount, actualKeyCount > threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testJsonDefinitionFields() throws Exception
|
||||||
|
{
|
||||||
|
sendGetReqAndInitRspData(pathToTestPdfNode, 200);
|
||||||
|
|
||||||
|
JSONObject jsonParsedObject = new JSONObject(new JSONTokener(jsonResponseString));
|
||||||
|
assertNotNull(jsonParsedObject);
|
||||||
|
|
||||||
|
JSONObject rootDataObject = (JSONObject)jsonParsedObject.get("data");
|
||||||
|
|
||||||
|
JSONObject definitionObject = (JSONObject)rootDataObject.get("definition");
|
||||||
|
|
||||||
|
JSONArray fieldsArray = (JSONArray)definitionObject.get("fields");
|
||||||
|
|
||||||
|
//TODO This will all be revamped when I introduce test code based on a known
|
||||||
|
// node. But in the meantime, I'll keep it general.
|
||||||
|
for (int i = 0; i < fieldsArray.length(); i++)
|
||||||
|
{
|
||||||
|
Object nextObj = fieldsArray.get(i);
|
||||||
|
|
||||||
|
JSONObject nextJsonObject = (JSONObject)nextObj;
|
||||||
|
List<String> fieldKeys = new ArrayList<String>();
|
||||||
|
for (Iterator iter2 = nextJsonObject.keys(); iter2.hasNext(); )
|
||||||
|
{
|
||||||
|
fieldKeys.add((String)iter2.next());
|
||||||
|
}
|
||||||
|
for (String s : fieldKeys)
|
||||||
|
{
|
||||||
|
if (s.equals("mandatory") || s.equals("protectedField"))
|
||||||
|
{
|
||||||
|
assertEquals("JSON booleans should be actual booleans.", java.lang.Boolean.class, nextJsonObject.get(s).getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendGetReqAndInitRspData(String url, int expectedStatusCode) throws IOException,
|
||||||
|
UnsupportedEncodingException
|
||||||
|
{
|
||||||
|
response = sendRequest(new GetRequest(url), expectedStatusCode);
|
||||||
|
jsonResponseString = response.getContentAsString();
|
||||||
|
}
|
||||||
|
}
|
BIN
source/java/org/alfresco/repo/web/scripts/forms/test_doc.pdf
Normal file
BIN
source/java/org/alfresco/repo/web/scripts/forms/test_doc.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user