From 2e94b28d0172e4a3d25da62de1ff97478d4a8c63 Mon Sep 17 00:00:00 2001 From: Gavin Cornwell Date: Thu, 19 Mar 2009 21:24:59 +0000 Subject: [PATCH] Added client and server side support for transient properties - As long as a template for a control is configured fields without a definition can now be displayed - Added 3 well known transient properties; mimetype, encoding and size (these are similar to the propertyResolvers we had in the JSF client) - Added explicit persistence handling for the new transient properties, the name property and adds aspect if title/description and/or author property is present - Added saveForm test git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13693 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/form-services-context.xml | 10 +- .../alfresco/messages/form-service.properties | 8 + .../alfresco/repo/forms/FieldDefinition.java | 2 +- .../repo/forms/FormServiceImplTest.java | 137 ++++- .../repo/forms/processor/NodeHandler.java | 485 +++++++++++++++--- .../repo/forms/script/test_formService.js | 2 +- 6 files changed, 573 insertions(+), 71 deletions(-) create mode 100644 config/alfresco/messages/form-service.properties diff --git a/config/alfresco/form-services-context.xml b/config/alfresco/form-services-context.xml index 6e28ab8668..0b400d249d 100644 --- a/config/alfresco/form-services-context.xml +++ b/config/alfresco/form-services-context.xml @@ -3,6 +3,14 @@ + + + + alfresco.messages.form-service + + + + @@ -76,6 +84,7 @@ parent="baseFormHandler"> + @@ -86,7 +95,6 @@ parent="baseFormHandler" /> --> - formService diff --git a/config/alfresco/messages/form-service.properties b/config/alfresco/messages/form-service.properties new file mode 100644 index 0000000000..fef6e474f9 --- /dev/null +++ b/config/alfresco/messages/form-service.properties @@ -0,0 +1,8 @@ +# form service externalised display strings + +form_service.mimetype.label=Mimetype +form_service.mimetype.description=Mimetype of the content +form_service.encoding.label=Encoding +form_service.encoding.description=Encoding of the content +form_service.size.label=Size +form_service.size.description=Size of the content in bytes \ No newline at end of file diff --git a/source/java/org/alfresco/repo/forms/FieldDefinition.java b/source/java/org/alfresco/repo/forms/FieldDefinition.java index 380d2943c8..1b9f17bf4d 100644 --- a/source/java/org/alfresco/repo/forms/FieldDefinition.java +++ b/source/java/org/alfresco/repo/forms/FieldDefinition.java @@ -37,7 +37,7 @@ public abstract class FieldDefinition protected String binding; protected String defaultValue; protected FieldGroup group; - protected boolean protectedField; + protected boolean protectedField = false; /** * Default constructor diff --git a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java index c780ffded7..ecd43ca5b0 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java @@ -34,11 +34,15 @@ import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.forms.AssociationFieldDefinition.Direction; import org.alfresco.repo.forms.FormData.FieldData; -import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.forms.PropertyFieldDefinition.FieldConstraint; +import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.repository.ContentData; +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.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; @@ -61,9 +65,11 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest private NamespaceService namespaceService; private ScriptService scriptService; private PersonService personService; + private ContentService contentService; private NodeRef document; private NodeRef associatedDoc; + private String documentName; private static String VALUE_TITLE = "This is the title for the test document"; private static String VALUE_DESCRIPTION = "This is the description for the test document"; @@ -72,11 +78,17 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest private static String VALUE_ADDRESSEES1 = "harry@example.com"; private static String VALUE_ADDRESSEES2 = "jane@example.com"; private static String VALUE_SUBJECT = "The subject is..."; + private static String VALUE_MIMETYPE = MimetypeMap.MIMETYPE_TEXT_PLAIN; + private static String VALUE_ENCODING = "UTF-8"; + private static String VALUE_CONTENT = "This is the content for the test document"; private static Date VALUE_SENT_DATE = new Date(); private static String LABEL_NAME = "Name"; private static String LABEL_TITLE = "Title"; private static String LABEL_DESCRIPTION = "Description"; + private static String LABEL_MIMETYPE = "Mimetype"; + private static String LABEL_ENCODING = "Encoding"; + private static String LABEL_SIZE = "Size"; private static String LABEL_ORIGINATOR = "Originator"; private static String LABEL_ADDRESSEE = "Addressee"; private static String LABEL_ADDRESSEES = "Addressees"; @@ -100,6 +112,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest this.namespaceService = (NamespaceService)this.applicationContext.getBean("NamespaceService"); this.scriptService = (ScriptService)this.applicationContext.getBean("ScriptService"); this.personService = (PersonService)this.applicationContext.getBean("PersonService"); + this.contentService = (ContentService)this.applicationContext.getBean("ContentService"); AuthenticationComponent authenticationComponent = (AuthenticationComponent) this.applicationContext .getBean("authenticationComponent"); @@ -124,7 +137,8 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // Create a node Map docProps = new HashMap(1); - docProps.put(ContentModel.PROP_NAME, "testDocument" + guid + ".txt"); + this.documentName = "testDocument" + guid + ".txt"; + docProps.put(ContentModel.PROP_NAME, this.documentName); this.document = this.nodeService.createNode( folder, ContentModel.ASSOC_CONTAINS, @@ -141,6 +155,12 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest ContentModel.TYPE_CONTENT, docProps).getChildRef(); + // add some content to the node + ContentWriter writer = this.contentService.getWriter(this.document, ContentModel.PROP_CONTENT, true); + writer.setMimetype(VALUE_MIMETYPE); + writer.setEncoding(VALUE_ENCODING); + writer.putContent(VALUE_CONTENT); + // add standard titled aspect Map aspectProps = new HashMap(2); aspectProps.put(ContentModel.PROP_TITLE, VALUE_TITLE); @@ -164,8 +184,8 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest this.nodeService.addAspect(document, ContentModel.ASPECT_REFERENCING, aspectProps); this.nodeService.createAssociation(this.document, this.associatedDoc, ContentModel.ASSOC_REFERENCES); - setComplete(); - endTransaction(); + //setComplete(); + //endTransaction(); } private void createUser(String userName) @@ -206,7 +226,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // check the field definitions Collection fieldDefs = form.getFieldDefinitions(); assertNotNull("Expecting to find fields", fieldDefs); - assertEquals("Expecting to find 19 fields", 19, fieldDefs.size()); + assertEquals("Expecting to find 22 fields", 22, fieldDefs.size()); // create a Map of the field definitions // NOTE: we can safely do this as we know there are no duplicate field names and we're not @@ -221,6 +241,9 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest PropertyFieldDefinition nameField = (PropertyFieldDefinition)fieldDefMap.get("cm:name"); PropertyFieldDefinition titleField = (PropertyFieldDefinition)fieldDefMap.get("cm:title"); PropertyFieldDefinition descField = (PropertyFieldDefinition)fieldDefMap.get("cm:description"); + PropertyFieldDefinition mimetypeField = (PropertyFieldDefinition)fieldDefMap.get("mimetype"); + PropertyFieldDefinition encodingField = (PropertyFieldDefinition)fieldDefMap.get("encoding"); + PropertyFieldDefinition sizeField = (PropertyFieldDefinition)fieldDefMap.get("size"); PropertyFieldDefinition originatorField = (PropertyFieldDefinition)fieldDefMap.get("cm:originator"); PropertyFieldDefinition addresseeField = (PropertyFieldDefinition)fieldDefMap.get("cm:addressee"); PropertyFieldDefinition addresseesField = (PropertyFieldDefinition)fieldDefMap.get("cm:addressees"); @@ -232,6 +255,9 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertNotNull("Expecting to find the cm:name field", nameField); assertNotNull("Expecting to find the cm:title field", titleField); assertNotNull("Expecting to find the cm:description field", descField); + assertNotNull("Expecting to find the mimetype field", mimetypeField); + assertNotNull("Expecting to find the encoding field", encodingField); + assertNotNull("Expecting to find the size field", sizeField); assertNotNull("Expecting to find the cm:originator field", originatorField); assertNotNull("Expecting to find the cm:addressee field", addresseeField); assertNotNull("Expecting to find the cm:addressees field", addresseesField); @@ -246,6 +272,12 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest LABEL_TITLE, titleField.getLabel()); assertEquals("Expecting cm:description label to be " + LABEL_DESCRIPTION, LABEL_DESCRIPTION, descField.getLabel()); + assertEquals("Expecting mimetype label to be " + LABEL_MIMETYPE, + LABEL_MIMETYPE, mimetypeField.getLabel()); + assertEquals("Expecting encoding label to be " + LABEL_ENCODING, + LABEL_ENCODING, encodingField.getLabel()); + assertEquals("Expecting size label to be " + LABEL_SIZE, + LABEL_SIZE, sizeField.getLabel()); assertEquals("Expecting cm:originator label to be " + LABEL_ORIGINATOR, LABEL_ORIGINATOR, originatorField.getLabel()); assertEquals("Expecting cm:addressee label to be " + LABEL_ADDRESSEE, @@ -299,11 +331,14 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertNotNull("Expecting field data", fieldData); assertEquals(VALUE_TITLE, fieldData.get("prop:cm:title").getValue()); assertEquals(VALUE_DESCRIPTION, fieldData.get("prop:cm:description").getValue()); + assertEquals(VALUE_MIMETYPE, fieldData.get("prop:mimetype").getValue()); + assertEquals(VALUE_ENCODING, fieldData.get("prop:encoding").getValue()); assertEquals(VALUE_ORIGINATOR, fieldData.get("prop:cm:originator").getValue()); assertEquals(VALUE_ADDRESSEE, fieldData.get("prop:cm:addressee").getValue()); assertEquals(VALUE_ADDRESSEES1, fieldData.get("prop:cm:addressees_0").getValue()); assertEquals(VALUE_ADDRESSEES2, fieldData.get("prop:cm:addressees_1").getValue()); assertEquals(VALUE_SUBJECT, fieldData.get("prop:cm:subjectline").getValue()); + assertTrue("Expecting size to be > 0", ((Long)fieldData.get("prop:size").getValue()).longValue() > 0); Calendar calTestValue = Calendar.getInstance(); calTestValue.setTime(VALUE_SENT_DATE); @@ -316,6 +351,98 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertEquals(this.associatedDoc.toString(), targets.get(0)); } + public void testSaveForm() throws Exception + { + // create FormData object containing the values to update + FormData data = new FormData(); + + // update the name + String newName = "new-" + this.documentName; + data.addData("prop:cm:name", newName); + + // update the title property + String newTitle = "This is the new title property"; + data.addData("prop:cm:title", newTitle); + + // update the mimetype + String newMimetype = MimetypeMap.MIMETYPE_HTML; + data.addData("prop:mimetype", newMimetype); + + // update the originator + String newOriginator = "jane@example.com"; + data.addData("prop:cm:originator", newOriginator); + + // set the date to null (using an empty string) + data.addData("prop:cm:sentdate", ""); + + // try and update non-existent properties (make sure there are no exceptions) + data.addData("prop:cm:wrong", "This should not be persisted"); + data.addData("cm:wrong", "This should not be persisted"); + + // persist the data + this.formService.saveForm(this.document.toString(), data); + + // retrieve the data directly from the node service to ensure its been changed + Map updatedProps = this.nodeService.getProperties(this.document); + String updatedName = (String)updatedProps.get(ContentModel.PROP_NAME); + String updatedTitle = (String)updatedProps.get(ContentModel.PROP_TITLE); + String updatedOriginator = (String)updatedProps.get(ContentModel.PROP_ORIGINATOR); + String wrong = (String)updatedProps.get(QName.createQName("cm", "wrong", this.namespaceService)); + Date sentDate = (Date)updatedProps.get(ContentModel.PROP_SENTDATE); + assertEquals(newName, updatedName); + assertEquals(newTitle, updatedTitle); + assertEquals(newOriginator, updatedOriginator); + assertNull("Expecting sentdate to be null", sentDate); + assertNull("Expecting my:wrong to be null", wrong); + + // check mimetype was updated + ContentData contentData = (ContentData)updatedProps.get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + String updatedMimetype = contentData.getMimetype(); + assertEquals(MimetypeMap.MIMETYPE_HTML, updatedMimetype); + } + } + + public void testNoForm() throws Exception + { + // test that a form can not be retrieved for a non-existent item + try + { + this.formService.getForm("Invalid Item"); + fail("Expecting getForm for 'Invalid Item' to fail"); + } + catch (Exception e) + { + // expected + } + + String missingNode = this.document.toString().replace("-", "x"); + Form form = this.formService.getForm(missingNode); + assertNull("Expecting getForm for a missing node to be null", form); + + // test that a form can not be saved for a non-existent item + try + { + this.formService.saveForm("Invalid Item", new FormData()); + fail("Expecting saveForm for 'Invalid Item' to fail"); + } + catch (Exception e) + { + // expected + } + + try + { + this.formService.saveForm(missingNode, new FormData()); + fail("Expecting saveForm for a missing node to fail"); + } + catch (Exception e) + { + // expected + } + } + public void off_testSaveUpdatedForm() throws Exception { fail("Form persistence not yet impl'd."); diff --git a/source/java/org/alfresco/repo/forms/processor/NodeHandler.java b/source/java/org/alfresco/repo/forms/processor/NodeHandler.java index 1f447bad1e..03d98acaf0 100644 --- a/source/java/org/alfresco/repo/forms/processor/NodeHandler.java +++ b/source/java/org/alfresco/repo/forms/processor/NodeHandler.java @@ -32,6 +32,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; import org.alfresco.repo.forms.AssociationFieldDefinition; import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.FormData; @@ -43,10 +45,15 @@ import org.alfresco.repo.forms.PropertyFieldDefinition.FieldConstraint; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileNotFoundException; 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.NodeService; import org.alfresco.service.namespace.NamespaceService; @@ -70,17 +77,43 @@ public class NodeHandler extends AbstractHandler protected static final String PROP_PREFIX = "prop:"; protected static final String ASSOC_PREFIX = "assoc:"; + protected static final String TRANSIENT_MIMETYPE = "mimetype"; + protected static final String TRANSIENT_SIZE = "size"; + protected static final String TRANSIENT_ENCODING = "encoding"; + + protected static final String MSG_MIMETYPE_LABEL = "form_service.mimetype.label"; + protected static final String MSG_MIMETYPE_DESC = "form_service.mimetype.description"; + protected static final String MSG_ENCODING_LABEL = "form_service.encoding.label"; + protected static final String MSG_ENCODING_DESC = "form_service.encoding.description"; + protected static final String MSG_SIZE_LABEL = "form_service.size.label"; + protected static final String MSG_SIZE_DESC = "form_service.size.description"; + /** Services */ protected NodeService nodeService; + protected FileFolderService fileFolderService; protected DictionaryService dictionaryService; protected NamespaceService namespaceService; /** - * A regular expression which can be used to match property/association names. + * A regular expression which can be used to match property names. * These names will look like "prop:cm:name". * The pattern can also be used to extract the "cm" and the "name" parts. */ - private Pattern propertyNamePattern = Pattern.compile("prop:(.*){1}?:(.*){1}?"); + protected Pattern propertyNamePattern = Pattern.compile(PROP_PREFIX + "(.*){1}?:(.*){1}?"); + + /** + * A regular expression which can be used to match tranisent property names. + * These names will look like "prop:name". + * The pattern can also be used to extract the "name" part. + */ + protected Pattern transientPropertyPattern = Pattern.compile(PROP_PREFIX + "(.*){1}?"); + + /** + * A regular expression which can be used to match association names. + * These names will look like "assoc:cm:references". + * The pattern can also be used to extract the "cm" and the "name" parts. + */ + protected Pattern associationNamePattern = Pattern.compile(ASSOC_PREFIX + "(.*){1}?:(.*){1}?"); /** * Sets the node service @@ -91,6 +124,16 @@ public class NodeHandler extends AbstractHandler { this.nodeService = nodeService; } + + /** + * Sets the file folder service + * + * @param fileFolderService The FileFolderService instance + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } /** * Sets the data dictionary service @@ -123,12 +166,8 @@ public class NodeHandler extends AbstractHandler // cast to the expected NodeRef representation NodeRef nodeRef = (NodeRef)item; - // set the type - QName type = this.nodeService.getType(nodeRef); - form.setType(type.toPrefixString(this.namespaceService)); - - // setup field definitions and data - setupFields(nodeRef, form); + // generate the form for the node + generateNode(nodeRef, form); if (logger.isDebugEnabled()) logger.debug("Returning form: " + form); @@ -141,70 +180,93 @@ public class NodeHandler extends AbstractHandler */ public void handlePersist(Object item, FormData data) { + if (logger.isDebugEnabled()) + logger.debug("Persisting form for: " + item); + // cast to the expected NodeRef representation NodeRef nodeRef = (NodeRef)item; - Map submittedProperties = extractSubmittedPropsFrom(data); - - // The call to addProperties changes the repo values of those properties - // included in the Map, but leaves any other property values unchanged. - // - // Compare setProperties(..), which causes the deletion of properties that - // are not included in the Map. - this.nodeService.addProperties(nodeRef, submittedProperties); + // persist the node + persistNode(nodeRef, data); } - private Map extractSubmittedPropsFrom(FormData data) + /** + * Sets up the Form object for the given NodeRef + * + * @param nodeRef The NodeRef to generate a Form for + * @param form The Form instance to populate + */ + protected void generateNode(NodeRef nodeRef, Form form) { - Map result = new HashMap(); - for (String dataKey : data.getData().keySet()) - { - FieldData nextFieldData = data.getData().get(dataKey); - if (nextFieldData.isFile()) - { - //TODO Implement support for file-based submits. - } - else - { - String nextDataName = nextFieldData.getName(); - - Matcher m = this.propertyNamePattern.matcher(nextDataName); - // Only "prop:" properties are handled here - if (m.matches()) - { - String qNamePrefix = m.group(1); - String localName = m.group(2); - QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); - // These values are all Strings. The repo does most of the data - // conversion work for us. - Object nextDataObject = nextFieldData.getValue(); - - // This cast should be safe as all dataObjects are Strings. - result.put(fullQName, (Serializable)nextDataObject); - } - //TODO Implement support for "assoc:" properties. - else - { - if (logger.isWarnEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Field ").append(nextDataName).append(" has not been recognised."); - logger.warn(msg.toString()); - } - } - } - } - return result; + // set the type + QName type = this.nodeService.getType(nodeRef); + form.setType(type.toPrefixString(this.namespaceService)); + + // setup field definitions and data + FormData formData = new FormData(); + generatePropertyFields(nodeRef, form, formData); + generateAssociationFields(nodeRef, form, formData); + generateTransientFields(nodeRef, form, formData); + form.setFormData(formData); } /** - * Sets up the field definitions for the form + * Persists the given FormData on the given NodeRef + * + * @param nodeRef The NodeRef to persist the form data on + * @param data The FormData to persist + */ + protected void persistNode(NodeRef nodeRef, FormData data) + { + // get the property definitions for the type of node being persisted + QName type = this.nodeService.getType(nodeRef); + TypeDefinition typeDef = this.dictionaryService.getAnonymousType( + type, this.nodeService.getAspects(nodeRef)); + Map propDefs = typeDef.getProperties(); + + Map propsToPersist = new HashMap(data.getData().size()); + + for (String dataKey : data.getData().keySet()) + { + FieldData fieldData = data.getData().get(dataKey); + // NOTE: ignore file fields for now, not supported yet! + if (fieldData.isFile() == false) + { + String fieldName = fieldData.getName(); + + if (fieldName.startsWith(PROP_PREFIX)) + { + processPropertyPersist(nodeRef, propDefs, fieldData, propsToPersist); + } + else if (fieldName.startsWith(ASSOC_PREFIX)) + { + // TODO: process any associations present + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignoring unrecognised field '" + fieldName + "'"); + } + } + } + + // persist the properties using addProperties as this changes the repo values of + // those properties included in the Map, but leaves any other property values unchanged, + // whereas setProperties causes the deletion of properties that are not included in the Map. + this.nodeService.addProperties(nodeRef, propsToPersist); + + // TODO: persist the associations + } + + /** + * Sets up the field definitions for the node's properties. + * + * @param nodeRef The NodeRef of the node being setup + * @param form The Form instance to populate + * @param formData The FormData instance to populate */ @SuppressWarnings("unchecked") - private void setupFields(NodeRef nodeRef, Form form) + protected void generatePropertyFields(NodeRef nodeRef, Form form, FormData formData) { - FormData formData = new FormData(); - // get data dictionary definition for node QName type = this.nodeService.getType(nodeRef); TypeDefinition typeDef = this.dictionaryService.getAnonymousType( @@ -283,7 +345,18 @@ public class NodeHandler extends AbstractHandler } } } - + } + + /** + * Sets up the field definitions for the node's associations. + * + * @param nodeRef The NodeRef of the node being setup + * @param form The Form instance to populate + * @param formData The FormData instance to populate + */ + @SuppressWarnings("unchecked") + protected void generateAssociationFields(NodeRef nodeRef, Form form, FormData formData) + { // add target association data List associations = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); @@ -352,8 +425,294 @@ public class NodeHandler extends AbstractHandler } // TODO: Add source association definitions and data + } + + /** + * Sets up the field definitions for any transient fields that may be + * useful, for example, 'mimetype', 'size' and 'encoding'. + * + * @param nodeRef The NodeRef of the node being setup + * @param form The Form instance to populate + * @param formData The FormData instance to populate + */ + protected void generateTransientFields(NodeRef nodeRef, Form form, FormData formData) + { + // if the node is content add the 'mimetype', 'size' and 'encoding' fields. + QName type = this.nodeService.getType(nodeRef); + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + ContentData content = (ContentData)this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (content != null) + { + // setup mimetype field + PropertyFieldDefinition mimetypeField = new PropertyFieldDefinition( + TRANSIENT_MIMETYPE, DataTypeDefinition.TEXT.toPrefixString( + this.namespaceService)); + mimetypeField.setLabel(I18NUtil.getMessage(MSG_MIMETYPE_LABEL)); + mimetypeField.setDescription(I18NUtil.getMessage(MSG_MIMETYPE_DESC)); + form.addFieldDefinition(mimetypeField); + formData.addData(PROP_PREFIX + TRANSIENT_MIMETYPE, content.getMimetype()); + + // setup encoding field + PropertyFieldDefinition encodingField = new PropertyFieldDefinition( + TRANSIENT_ENCODING, DataTypeDefinition.TEXT.toPrefixString( + this.namespaceService)); + encodingField.setLabel(I18NUtil.getMessage(MSG_ENCODING_LABEL)); + encodingField.setDescription(I18NUtil.getMessage(MSG_ENCODING_DESC)); + form.addFieldDefinition(encodingField); + formData.addData(PROP_PREFIX + TRANSIENT_ENCODING, content.getEncoding()); + + // setup size field + PropertyFieldDefinition sizeField = new PropertyFieldDefinition( + TRANSIENT_SIZE, DataTypeDefinition.LONG.toPrefixString( + this.namespaceService)); + sizeField.setLabel(I18NUtil.getMessage(MSG_SIZE_LABEL)); + sizeField.setDescription(I18NUtil.getMessage(MSG_SIZE_DESC)); + sizeField.setProtectedField(true); + form.addFieldDefinition(sizeField); + formData.addData(PROP_PREFIX + TRANSIENT_SIZE, new Long(content.getSize())); + } + } + } + + /** + * Processes the given field data for persistence as a property. + * + * @param nodeRef The NodeRef to persist the properties on + * @param propDefs Map of PropertyDefinition's for the node being persisted + * @param fieldData Data to persist for the property + * @param propsToPersist Map of properties to be persisted + */ + protected void processPropertyPersist(NodeRef nodeRef, Map propDefs, + FieldData fieldData, Map propsToPersist) + { + if (logger.isDebugEnabled()) + logger.debug("Processing field " + fieldData + " for property persistence"); - // set the form data - form.setFormData(formData); + // match and extract the prefix and name parts + Matcher m = this.propertyNamePattern.matcher(fieldData.getName()); + if (m.matches()) + { + String qNamePrefix = m.group(1); + String localName = m.group(2); + QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); + + // ensure that the property being persisted is defined in the model + PropertyDefinition propDef = propDefs.get(fullQName); + if (propDef != null) + { + // look for properties that have well known handling requirements + if (fullQName.equals(ContentModel.PROP_NAME)) + { + processNamePropertyPersist(nodeRef, fieldData); + } + else if (fullQName.equals(ContentModel.PROP_TITLE)) + { + processTitlePropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (fullQName.equals(ContentModel.PROP_DESCRIPTION)) + { + processDescriptionPropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (fullQName.equals(ContentModel.PROP_AUTHOR)) + { + processAuthorPropertyPersist(nodeRef, fieldData, propsToPersist); + } + else + { + Object value = fieldData.getValue(); + + // before persisting check data type of property, if it's numerical + // or a date ensure empty strings are changed to null and convert + // locale strings to locale objects + if ((value instanceof String) && ((String)value).length() == 0) + { + if (propDef.getDataType().getName().equals(DataTypeDefinition.DOUBLE) || + propDef.getDataType().getName().equals(DataTypeDefinition.FLOAT) || + propDef.getDataType().getName().equals(DataTypeDefinition.INT) || + propDef.getDataType().getName().equals(DataTypeDefinition.LONG) || + propDef.getDataType().getName().equals(DataTypeDefinition.DATE) || + propDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)) + { + value = null; + } + } + else if (propDef.getDataType().getName().equals(DataTypeDefinition.LOCALE)) + { + value = I18NUtil.parseLocale((String)value); + } + + // add the property to the map + propsToPersist.put(fullQName, (Serializable)value); + } + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignoring field '" + fieldData.getName() + "' as a property definition can not be found"); + } + } + else + { + // the field is potentially a well know transient property + // check for the ones we know about, anything else is ignored + Matcher tppm = this.transientPropertyPattern.matcher(fieldData.getName()); + if (tppm.matches()) + { + String fieldName = tppm.group(1); + + if (fieldName.equals(TRANSIENT_MIMETYPE)) + { + processMimetypePropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (fieldName.equals(TRANSIENT_ENCODING)) + { + processEncodingPropertyPersist(nodeRef, fieldData, propsToPersist); + } + else if (fieldName.equals(TRANSIENT_SIZE)) + { + // the size property is well known but should never be persisted + // as it is calculated so this is intentionally ignored + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignoring unrecognised field '" + fieldData.getName() + "'"); + } + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignoring unrecognised field '" + fieldData.getName() + "'"); + } + } + } + + /** + * Persists the given field data as the name property + * + * @param nodeRef The NodeRef to update the name for + * @param fieldData The data representing the new name value + */ + protected void processNamePropertyPersist(NodeRef nodeRef, FieldData fieldData) + { + try + { + // if the name property changes the rename method of the file folder + // service should be called rather than updating the property directly + this.fileFolderService.rename(nodeRef, (String)fieldData.getValue()); + } + catch (FileExistsException fee) + { + throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fee); + } + catch (FileNotFoundException fnne) + { + throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fnne); + } + } + + /** + * Persists the given field data as the title property + * + * @param nodeRef The NodeRef to update the title for + * @param fieldData The data representing the new title value + * @param propsToPersist Map of properties to be persisted + */ + protected void processTitlePropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + // if a title property is present ensure the 'titled' aspect is applied + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TITLED) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, null); + } + + propsToPersist.put(ContentModel.PROP_TITLE, (String)fieldData.getValue()); + } + + /** + * Persists the given field data as the description property + * + * @param nodeRef The NodeRef to update the description for + * @param fieldData The data representing the new description value + * @param propsToPersist Map of properties to be persisted + */ + protected void processDescriptionPropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + // if a description property is present ensure the 'titled' aspect is applied + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TITLED) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, null); + } + + propsToPersist.put(ContentModel.PROP_DESCRIPTION, (String)fieldData.getValue()); + } + + /** + * Persists the given field data as the author property + * + * @param nodeRef The NodeRef to update the author for + * @param fieldData The data representing the new author value + * @param propsToPersist Map of properties to be persisted + */ + protected void processAuthorPropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + // if an author property is present ensure the 'author' aspect is applied + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUTHOR) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_AUTHOR, null); + } + + propsToPersist.put(ContentModel.PROP_AUTHOR, (String)fieldData.getValue()); + } + + /** + * Persists the given field data as the mimetype property + * + * @param nodeRef The NodeRef to update the mimetype for + * @param fieldData The data representing the new mimetype value + * @param propsToPersist Map of properties to be persisted + */ + protected void processMimetypePropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + ContentData contentData = (ContentData)propsToPersist.get(ContentModel.PROP_CONTENT); + if (contentData == null) + { + // content data has not been persisted yet so get it from the node + contentData = (ContentData)this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + } + + if (contentData != null) + { + // update content data if we found the property + contentData = ContentData.setMimetype(contentData, (String)fieldData.getValue()); + propsToPersist.put(ContentModel.PROP_CONTENT, contentData); + } + } + + /** + * Persists the given field data as the encoding property + * + * @param nodeRef The NodeRef to update the encoding for + * @param fieldData The data representing the new encoding value + * @param propsToPersist Map of properties to be persisted + */ + protected void processEncodingPropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) + { + ContentData contentData = (ContentData)propsToPersist.get(ContentModel.PROP_CONTENT); + if (contentData == null) + { + // content data has not been persisted yet so get it from the node + contentData = (ContentData)this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + } + + if (contentData != null) + { + // update content data if we found the property + contentData = ContentData.setEncoding(contentData, (String)fieldData.getValue()); + propsToPersist.put(ContentModel.PROP_CONTENT, contentData); + } } } diff --git a/source/java/org/alfresco/repo/forms/script/test_formService.js b/source/java/org/alfresco/repo/forms/script/test_formService.js index aebc69a839..8126e6ba57 100644 --- a/source/java/org/alfresco/repo/forms/script/test_formService.js +++ b/source/java/org/alfresco/repo/forms/script/test_formService.js @@ -22,7 +22,7 @@ function testGetFormForContentNode() var fieldDefs = form.fieldDefinitions; test.assertNotNull(fieldDefs, "field definitions should not be null."); - test.assertEquals(19, fieldDefs.length); + test.assertEquals(22, fieldDefs.length); var fieldDefnDataHash = form.fieldDefinitionData;