diff --git a/config/alfresco/form-services-context.xml b/config/alfresco/form-services-context.xml index 0b400d249d..cf7f1a4751 100644 --- a/config/alfresco/form-services-context.xml +++ b/config/alfresco/form-services-context.xml @@ -71,7 +71,7 @@ - workspace://[\w\-\/]* + node @@ -99,7 +99,6 @@ formService - diff --git a/source/java/org/alfresco/repo/forms/Form.java b/source/java/org/alfresco/repo/forms/Form.java index 650f985394..ac976f137a 100644 --- a/source/java/org/alfresco/repo/forms/Form.java +++ b/source/java/org/alfresco/repo/forms/Form.java @@ -35,7 +35,7 @@ import java.util.List; */ public class Form { - protected String item; + protected Item item; protected String type; protected List fieldDefinitions; protected Collection fieldGroups; @@ -44,20 +44,19 @@ public class Form /** * Constructs a Form * - * @param item An identifier for the item the form is for + * @param item The item the form is for */ - public Form(String item) + public Form(Item item) { this.item = item; } /** - * Returns an identifier for the item the form is for, in the case of a node - * it will be a NodeRef, for a task, a task id etc. + * Returns the item the form is for * * @return The item */ - public String getItem() + public Item getItem() { return this.item; } @@ -167,7 +166,8 @@ public class Form } /** - * Sets the data this form should display + * Sets the data this form should display. This will overwrite + * any existing form data being held * * @param data FormData instance containing the data */ @@ -176,6 +176,22 @@ public class Form this.data = data; } + /** + * Adds some data to be displayed by the form + * + * @param fieldName Name of the field the data is for + * @param fieldData The value + */ + public void addData(String fieldName, Object fieldData) + { + if (this.data == null) + { + this.data = new FormData(); + } + + this.data.addData(fieldName, fieldData); + } + /* * @see java.lang.Object#toString() */ diff --git a/source/java/org/alfresco/repo/forms/FormException.java b/source/java/org/alfresco/repo/forms/FormException.java index 8250bc23eb..11b0cf1d66 100644 --- a/source/java/org/alfresco/repo/forms/FormException.java +++ b/source/java/org/alfresco/repo/forms/FormException.java @@ -35,13 +35,13 @@ public class FormException extends AlfrescoRuntimeException { private static final long serialVersionUID = 688834574410335422L; - public FormException(String msg) + public FormException(String msgId) { - super(msg); + super(msgId); } - public FormException(String msg, Throwable cause) + public FormException(String msgId, Throwable cause) { - super(msg, cause); + super(msgId, cause); } } diff --git a/source/java/org/alfresco/repo/forms/FormNotFoundException.java b/source/java/org/alfresco/repo/forms/FormNotFoundException.java new file mode 100644 index 0000000000..632a122cf7 --- /dev/null +++ b/source/java/org/alfresco/repo/forms/FormNotFoundException.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.forms; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Exception used by the Form service when a form can not be found for the given item + * + * @author Gavin Cornwell + */ +public class FormNotFoundException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 688834574410335422L; + + public FormNotFoundException(Item item) + { + // TODO: replace strings with msg ids + super("A form could not be found for item: " + item); + } + + public FormNotFoundException(Item item, Throwable cause) + { + // TODO: replace strings with msg ids + super("A form could not be found for item: " + item, cause); + } +} diff --git a/source/java/org/alfresco/repo/forms/FormService.java b/source/java/org/alfresco/repo/forms/FormService.java index cf103861ea..34794193b1 100644 --- a/source/java/org/alfresco/repo/forms/FormService.java +++ b/source/java/org/alfresco/repo/forms/FormService.java @@ -24,6 +24,8 @@ */ package org.alfresco.repo.forms; +import java.util.List; + /** * Form service API. @@ -35,18 +37,47 @@ package org.alfresco.repo.forms; public interface FormService { /** - * Returns a form representation of the given item + * Returns a form representation of the given item, + * all known fields for the item are included. * * @param item The item to get a form for * @return The Form representation */ - public Form getForm(String item); + public Form getForm(Item item); /** - * Persists the given form representation for the given item + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param item The item to get a form for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @return The Form representation + */ + public Form getForm(Item item, List fields); + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param item The item to get a form for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @param forcedFields List of field names from 'fields' list + * that should be forcibly included, it is + * up to the form processor implementation + * to determine how to enforce this + * @return The Form representation + */ + public Form getForm(Item item, List fields, List forcedFields); + + /** + * Persists the given form representation for the given item. * * @param item The item to persist the form for * @param data An object representing the form data to persist */ - public void saveForm(String item, FormData data); + public void saveForm(Item item, FormData data); } diff --git a/source/java/org/alfresco/repo/forms/FormServiceImpl.java b/source/java/org/alfresco/repo/forms/FormServiceImpl.java index c55d9dee33..46912e259d 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImpl.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImpl.java @@ -24,9 +24,10 @@ */ package org.alfresco.repo.forms; +import java.util.List; + import org.alfresco.repo.forms.processor.FormProcessor; import org.alfresco.repo.forms.processor.FormProcessorRegistry; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,10 +54,26 @@ public class FormServiceImpl implements FormService this.processorRegistry = registry; } - /** - * @see org.alfresco.repo.forms.FormService#getForm(java.lang.String) + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item) */ - public Form getForm(String item) + public Form getForm(Item item) + { + return getForm(item, null, null); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List) + */ + public Form getForm(Item item, List fields) + { + return getForm(item, fields, null); + } + + /* + * @see org.alfresco.repo.forms.FormService#getForm(org.alfresco.repo.forms.Item, java.util.List, java.util.List) + */ + public Form getForm(Item item, List fields, List forcedFields) { if (this.processorRegistry == null) { @@ -74,26 +91,14 @@ public class FormServiceImpl implements FormService } else { - Form result = null; - try - { - result = processor.generate(item); - } - catch (InvalidNodeRefException inrx) - { - if (logger.isDebugEnabled()) - { - logger.debug(inrx); - } - } - return result; + return processor.generate(item, fields, forcedFields); } } - + /* - * @see org.alfresco.repo.forms.FormService#saveForm(java.lang.String, org.alfresco.repo.forms.FormData) + * @see org.alfresco.repo.forms.FormService#saveForm(org.alfresco.repo.forms.Item, org.alfresco.repo.forms.FormData) */ - public void saveForm(String item, FormData data) + public void saveForm(Item item, FormData data) { if (this.processorRegistry == null) { diff --git a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java index e89b4a9c74..e34c093f13 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java @@ -69,7 +69,10 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest private NodeRef document; private NodeRef associatedDoc; + private NodeRef childDoc; + private NodeRef folder; private String documentName; + private String folderName; 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"; @@ -88,6 +91,9 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest private static String LABEL_NAME = "Name"; private static String LABEL_TITLE = "Title"; private static String LABEL_DESCRIPTION = "Description"; + private static String LABEL_AUTHOR = "Author"; + private static String LABEL_MODIFIED = "Modified Date"; + private static String LABEL_MODIFIER = "Modifier"; private static String LABEL_MIMETYPE = "Mimetype"; private static String LABEL_ENCODING = "Encoding"; private static String LABEL_SIZE = "Size"; @@ -97,9 +103,10 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest private static String LABEL_SUBJECT = "Subject"; private static String LABEL_SENT_DATE = "Sent Date"; private static String LABEL_REFERENCES = "References"; + private static String LABEL_CONTAINS = "Contains"; private static final String USER_ONE = "UserOne_SiteServiceImplTest"; - + private static final String NODE_FORM_ITEM_KIND = "node"; /** * Called during the transaction setup @@ -129,8 +136,9 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore")); Map folderProps = new HashMap(1); - folderProps.put(ContentModel.PROP_NAME, "testFolder" + guid); - NodeRef folder = this.nodeService.createNode( + this.folderName = "testFolder" + guid; + folderProps.put(ContentModel.PROP_NAME, this.folderName); + this.folder = this.nodeService.createNode( rootNode, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder" + guid), @@ -142,7 +150,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest this.documentName = "testDocument" + guid + ".txt"; docProps.put(ContentModel.PROP_NAME, this.documentName); this.document = this.nodeService.createNode( - folder, + this.folder, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testDocument" + guid + ".txt"), ContentModel.TYPE_CONTENT, @@ -151,12 +159,21 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // create a node to use as target of association docProps.put(ContentModel.PROP_NAME, "associatedDocument" + guid + ".txt"); this.associatedDoc = this.nodeService.createNode( - folder, + this.folder, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "associatedDocument" + guid + ".txt"), ContentModel.TYPE_CONTENT, docProps).getChildRef(); + // create a node to use as a 2nd child node of the folder + docProps.put(ContentModel.PROP_NAME, "childDocument" + guid + ".txt"); + this.childDoc = this.nodeService.createNode( + this.folder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "childDocument" + guid + ".txt"), + ContentModel.TYPE_CONTENT, + docProps).getChildRef(); + // add some content to the nodes ContentWriter writer = this.contentService.getWriter(this.document, ContentModel.PROP_CONTENT, true); writer.setMimetype(VALUE_MIMETYPE); @@ -190,9 +207,6 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest aspectProps.clear(); this.nodeService.addAspect(document, ContentModel.ASPECT_REFERENCING, aspectProps); this.nodeService.createAssociation(this.document, this.associatedDoc, ContentModel.ASSOC_REFERENCES); - -// setComplete(); -// endTransaction(); } private void createUser(String userName) @@ -213,15 +227,16 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest } @SuppressWarnings("unchecked") - public void testGetForm() throws Exception + public void testGetAllDocForm() throws Exception { - Form form = this.formService.getForm(this.document.toString()); + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, this.document.toString())); // check a form got returned assertNotNull("Expecting form to be present", form); // check item identifier matches - assertEquals(this.document.toString(), form.getItem()); + assertEquals(NODE_FORM_ITEM_KIND, form.getItem().getKind()); + assertEquals(this.document.toString(), form.getItem().getId()); // check the type is correct assertEquals(ContentModel.TYPE_CONTENT.toPrefixString(this.namespaceService), @@ -233,7 +248,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // check the field definitions Collection fieldDefs = form.getFieldDefinitions(); assertNotNull("Expecting to find fields", fieldDefs); - assertEquals("Expecting to find 23 fields", 23, 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 @@ -364,6 +379,339 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertEquals(this.associatedDoc.toString(), targets.get(0)); } + @SuppressWarnings("unchecked") + public void testGetSelectedFieldsDocForm() throws Exception + { + // define a list of fields to retrieve from the node + List fields = new ArrayList(8); + fields.add("cm:name"); + fields.add("cm:title"); + fields.add("mimetype"); + fields.add("cm:modified"); + fields.add("cm:modifier"); + fields.add("cm:subjectline"); + fields.add("cm:sentdate"); + fields.add("cm:references"); + + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, this.document.toString()), fields); + + // check a form got returned + assertNotNull("Expecting form to be present", form); + + // check item identifier matches + assertEquals(NODE_FORM_ITEM_KIND, form.getItem().getKind()); + assertEquals(this.document.toString(), form.getItem().getId()); + + // check the type is correct + assertEquals(ContentModel.TYPE_CONTENT.toPrefixString(this.namespaceService), + form.getType()); + + // check there is no group info + assertNull("Expecting the form groups to be null!", form.getFieldGroups()); + + // check the field definitions + Collection fieldDefs = form.getFieldDefinitions(); + assertNotNull("Expecting to find fields", fieldDefs); + assertEquals("Expecting to find " + fields.size() + " fields", fields.size(), 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 + // concerned with ordering! + Map fieldDefMap = new HashMap(fieldDefs.size()); + for (FieldDefinition fieldDef : fieldDefs) + { + fieldDefMap.put(fieldDef.getName(), fieldDef); + } + + // find the fields + PropertyFieldDefinition nameField = (PropertyFieldDefinition)fieldDefMap.get("cm:name"); + PropertyFieldDefinition titleField = (PropertyFieldDefinition)fieldDefMap.get("cm:title"); + PropertyFieldDefinition mimetypeField = (PropertyFieldDefinition)fieldDefMap.get("mimetype"); + PropertyFieldDefinition modifiedField = (PropertyFieldDefinition)fieldDefMap.get("cm:modified"); + PropertyFieldDefinition modifierField = (PropertyFieldDefinition)fieldDefMap.get("cm:modifier"); + PropertyFieldDefinition subjectField = (PropertyFieldDefinition)fieldDefMap.get("cm:subjectline"); + PropertyFieldDefinition sentDateField = (PropertyFieldDefinition)fieldDefMap.get("cm:sentdate"); + AssociationFieldDefinition referencesField = (AssociationFieldDefinition)fieldDefMap.get("cm:references"); + + // check fields are present + assertNotNull("Expecting to find the cm:name field", nameField); + assertNotNull("Expecting to find the cm:title field", titleField); + assertNotNull("Expecting to find the mimetype field", mimetypeField); + assertNotNull("Expecting to find the cm:modified field", modifiedField); + assertNotNull("Expecting to find the cm:modifier field", modifierField); + assertNotNull("Expecting to find the cm:subjectline field", subjectField); + assertNotNull("Expecting to find the cm:sentdate field", sentDateField); + assertNotNull("Expecting to find the cm:references field", referencesField); + + // check the labels of all the fields + assertEquals("Expecting cm:name label to be " + LABEL_NAME, + LABEL_NAME, nameField.getLabel()); + assertEquals("Expecting cm:title label to be " + LABEL_TITLE, + LABEL_TITLE, titleField.getLabel()); + assertEquals("Expecting mimetype label to be " + LABEL_MIMETYPE, + LABEL_MIMETYPE, mimetypeField.getLabel()); + assertEquals("Expecting cm:modified label to be " + LABEL_MODIFIED, + LABEL_MODIFIED, modifiedField.getLabel()); + assertEquals("Expecting cm:modifier label to be " + LABEL_MODIFIER, + LABEL_MODIFIER, modifierField.getLabel()); + assertEquals("Expecting cm:subjectline label to be " + LABEL_SUBJECT, + LABEL_SUBJECT, subjectField.getLabel()); + assertEquals("Expecting cm:sentdate label to be " + LABEL_SENT_DATE, + LABEL_SENT_DATE, sentDateField.getLabel()); + assertEquals("Expecting cm:references label to be " + LABEL_REFERENCES, + LABEL_REFERENCES, referencesField.getLabel()); + + // check the details of the modified field + assertEquals("Expecting cm:modified type to be d:datetime", "d:datetime", modifiedField.getDataType()); + assertTrue("Expecting cm:modified to be mandatory", modifiedField.isMandatory()); + assertFalse("Expecting cm:modified to be single valued", modifiedField.isRepeating()); + + // check the details of the association field + assertEquals("Expecting cm:references endpoint type to be cm:content", "cm:content", + referencesField.getEndpointType()); + assertEquals("Expecting cm:references endpoint direction to be TARGET", + Direction.TARGET.toString(), + referencesField.getEndpointDirection().toString()); + assertFalse("Expecting cm:references endpoint to be optional", + referencesField.isEndpointMandatory()); + assertTrue("Expecting cm:references endpoint to be 1 to many", + referencesField.isEndpointMany()); + + // check the form data + FormData data = form.getFormData(); + assertNotNull("Expecting form data", data); + Map fieldData = data.getData(); + assertNotNull("Expecting field data", fieldData); + assertEquals(this.documentName, fieldData.get("prop:cm:name").getValue()); + assertEquals(VALUE_TITLE, fieldData.get("prop:cm:title").getValue()); + assertEquals(VALUE_MIMETYPE, fieldData.get("prop:mimetype").getValue()); + assertEquals(VALUE_SUBJECT, fieldData.get("prop:cm:subjectline").getValue()); + assertEquals(USER_ONE, fieldData.get("prop:cm:modifier").getValue()); + + Date modifiedDate = (Date)fieldData.get("prop:cm:modified").getValue(); + assertNotNull("Expecting to find modified date", modifiedDate); + assertTrue("Expecting modified field to return a Date", (modifiedDate instanceof Date)); + + Calendar calTestValue = Calendar.getInstance(); + calTestValue.setTime(VALUE_SENT_DATE); + Calendar calServiceValue = Calendar.getInstance(); + calServiceValue.setTime((Date)fieldData.get("prop:cm:sentdate").getValue()); + assertEquals(calTestValue.getTimeInMillis(), calServiceValue.getTimeInMillis()); + + List targets = (List)fieldData.get("assoc:cm:references").getValue(); + assertEquals("Expecting 1 target", 1, targets.size()); + assertEquals(this.associatedDoc.toString(), targets.get(0)); + } + + public void testMissingFieldsDocForm() throws Exception + { + // define a list of fields to retrieve from the node + List fields = new ArrayList(8); + fields.add("cm:name"); + fields.add("cm:title"); + + // add fields that will not be present + fields.add("cm:author"); + fields.add("wrong-name"); + + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, this.document.toString()), fields); + + // check a form got returned + assertNotNull("Expecting form to be present", form); + + // check the field definitions + Collection fieldDefs = form.getFieldDefinitions(); + assertNotNull("Expecting to find fields", fieldDefs); + assertEquals("Expecting to find " + (fields.size()-2) + " fields", fields.size()-2, 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 + // concerned with ordering! + Map fieldDefMap = new HashMap(fieldDefs.size()); + for (FieldDefinition fieldDef : fieldDefs) + { + fieldDefMap.put(fieldDef.getName(), fieldDef); + } + + // find the fields + PropertyFieldDefinition nameField = (PropertyFieldDefinition)fieldDefMap.get("cm:name"); + PropertyFieldDefinition titleField = (PropertyFieldDefinition)fieldDefMap.get("cm:title"); + PropertyFieldDefinition authorField = (PropertyFieldDefinition)fieldDefMap.get("cm:author"); + + // check fields are present + assertNotNull("Expecting to find the cm:name field", nameField); + assertNotNull("Expecting to find the cm:title field", titleField); + assertNull("Expecting cm:author field to be missing", authorField); + + // check the labels of all the fields + assertEquals("Expecting cm:name label to be " + LABEL_NAME, + LABEL_NAME, nameField.getLabel()); + assertEquals("Expecting cm:title label to be " + LABEL_TITLE, + LABEL_TITLE, titleField.getLabel()); + + // check the form data + FormData data = form.getFormData(); + assertNotNull("Expecting form data", data); + Map fieldData = data.getData(); + assertNotNull("Expecting field data", fieldData); + assertEquals(this.documentName, fieldData.get("prop:cm:name").getValue()); + assertEquals(VALUE_TITLE, fieldData.get("prop:cm:title").getValue()); + } + + public void testForcedFieldsDocForm() throws Exception + { + // define a list of fields to retrieve from the node + List fields = new ArrayList(4); + fields.add("cm:name"); + fields.add("cm:title"); + + // add fields that will not be present + fields.add("cm:author"); + fields.add("cm:never"); + fields.add("wrong-name"); + + // try and force the missing fields to appear + List forcedFields = new ArrayList(2); + forcedFields.add("cm:author"); + forcedFields.add("cm:never"); + forcedFields.add("wrong-name"); + + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, this.document.toString()), fields, forcedFields); + + // check a form got returned + assertNotNull("Expecting form to be present", form); + + // check the field definitions + Collection fieldDefs = form.getFieldDefinitions(); + assertNotNull("Expecting to find fields", fieldDefs); + assertEquals("Expecting to find " + (fields.size()-2) + " fields", fields.size()-2, 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 + // concerned with ordering! + Map fieldDefMap = new HashMap(fieldDefs.size()); + for (FieldDefinition fieldDef : fieldDefs) + { + fieldDefMap.put(fieldDef.getName(), fieldDef); + } + + // find the fields + PropertyFieldDefinition nameField = (PropertyFieldDefinition)fieldDefMap.get("cm:name"); + PropertyFieldDefinition titleField = (PropertyFieldDefinition)fieldDefMap.get("cm:title"); + PropertyFieldDefinition authorField = (PropertyFieldDefinition)fieldDefMap.get("cm:author"); + PropertyFieldDefinition neverField = (PropertyFieldDefinition)fieldDefMap.get("cm:never"); + PropertyFieldDefinition wrongField = (PropertyFieldDefinition)fieldDefMap.get("wrong-name"); + + // check fields are present + assertNotNull("Expecting to find the cm:name field", nameField); + assertNotNull("Expecting to find the cm:title field", titleField); + assertNotNull("Expecting to find the cm:author field", authorField); + assertNull("Expecting cm:never field to be missing", neverField); + assertNull("Expecting wrong-name field to be missing", wrongField); + + // check the labels of all the fields + assertEquals("Expecting cm:name label to be " + LABEL_NAME, + LABEL_NAME, nameField.getLabel()); + assertEquals("Expecting cm:title label to be " + LABEL_TITLE, + LABEL_TITLE, titleField.getLabel()); + assertEquals("Expecting cm:author label to be " + LABEL_AUTHOR, + LABEL_AUTHOR, authorField.getLabel()); + + // check the form data + FormData data = form.getFormData(); + assertNotNull("Expecting form data", data); + Map fieldData = data.getData(); + assertNotNull("Expecting field data", fieldData); + assertEquals(this.documentName, fieldData.get("prop:cm:name").getValue()); + assertEquals(VALUE_TITLE, fieldData.get("prop:cm:title").getValue()); + assertNull("Didn't expect to find a value for cm:author", fieldData.get("prop:cm:author")); + } + + @SuppressWarnings("unchecked") + public void testGetAllFolderForm() throws Exception + { + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, this.folder.toString())); + + // check a form got returned + assertNotNull("Expecting form to be present", form); + + // check item identifier matches + assertEquals(NODE_FORM_ITEM_KIND, form.getItem().getKind()); + assertEquals(this.folder.toString(), form.getItem().getId()); + + // check the type is correct + assertEquals(ContentModel.TYPE_FOLDER.toPrefixString(this.namespaceService), + form.getType()); + + // check there is no group info + assertNull("Expecting the form groups to be null!", form.getFieldGroups()); + + // check the field definitions + Collection fieldDefs = form.getFieldDefinitions(); + assertNotNull("Expecting to find fields", fieldDefs); + assertEquals("Expecting to find 11 fields", 11, 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 + // concerned with ordering! + Map fieldDefMap = new HashMap(fieldDefs.size()); + for (FieldDefinition fieldDef : fieldDefs) + { + fieldDefMap.put(fieldDef.getName(), fieldDef); + } + + // find the fields + PropertyFieldDefinition nameField = (PropertyFieldDefinition)fieldDefMap.get("cm:name"); + AssociationFieldDefinition containsField = (AssociationFieldDefinition)fieldDefMap.get("cm:contains"); + + // check fields are present + assertNotNull("Expecting to find the cm:name field", nameField); + assertNotNull("Expecting to find the cm:contains field", containsField); + + // check the labels of all the fields + assertEquals("Expecting cm:name label to be " + LABEL_NAME, + LABEL_NAME, nameField.getLabel()); + assertEquals("Expecting cm:contains label to be " + LABEL_CONTAINS, + LABEL_CONTAINS, containsField.getLabel()); + + // check details of name field + assertEquals("Expecting cm:name type to be d:text", "d:text", nameField.getDataType()); + assertTrue("Expecting cm:name to be mandatory", nameField.isMandatory()); + assertFalse("Expecting cm:name to be single valued", nameField.isRepeating()); + + // check the details of the association field + assertEquals("Expecting cm:contains endpoint type to be sys:base", "sys:base", + containsField.getEndpointType()); + assertEquals("Expecting cm:contains endpoint direction to be TARGET", + Direction.TARGET.toString(), + containsField.getEndpointDirection().toString()); + assertFalse("Expecting cm:contains endpoint to be optional", + containsField.isEndpointMandatory()); + assertTrue("Expecting cm:contains endpoint to be 1 to many", + containsField.isEndpointMany()); + + // check the form data + FormData data = form.getFormData(); + assertNotNull("Expecting form data", data); + Map fieldData = data.getData(); + assertNotNull("Expecting field data", fieldData); + assertEquals(this.folderName, fieldData.get("prop:cm:name").getValue()); + + List children = (List)fieldData.get("assoc:cm:contains").getValue(); + assertEquals("Expecting 3 children", 3, children.size()); + assertEquals(this.document.toString(), children.get(0)); + assertEquals(this.associatedDoc.toString(), children.get(1)); + assertEquals(this.childDoc.toString(), children.get(2)); + } + + @SuppressWarnings("unchecked") + public void testGetSelectedFieldsFolderForm() throws Exception + { + // attempt to get a form with fields that are not appropriate + // for a folder type i.e. mimetype and encoding + + } + @SuppressWarnings("unchecked") public void testSaveForm() throws Exception { @@ -398,7 +746,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest data.addData("cm:wrong", "This should not be persisted"); // persist the data - this.formService.saveForm(this.document.toString(), data); + this.formService.saveForm(new Item(NODE_FORM_ITEM_KIND, this.document.toString()), data); // retrieve the data directly from the node service to ensure its been changed Map updatedProps = this.nodeService.getProperties(this.document); @@ -429,29 +777,13 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest } } -// public void testApplyAspectProgrammatically() throws Exception -// { -// NodeRef nrSrc = new NodeRef("workspace://SpacesStore/320c74ad-dc79-4812-adf1-8160c37fdecb"); -// NodeRef nrDest = new NodeRef("workspace://SpacesStore/5ac5ccac-3409-4f4a-9338-a39ee1acd2cb"); -// -// Map aspectProps = new HashMap(2); -// // add referencing aspect (has association) -// this.nodeService.addAspect(nrSrc, ContentModel.ASPECT_REFERENCING, aspectProps); -// this.nodeService.createAssociation(nrSrc, nrDest, ContentModel.ASSOC_REFERENCES); -// -// setComplete(); -// endTransaction(); -// -// System.out.println("did it work?"); -// } -// 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"); + this.formService.getForm(new Item("Invalid Kind", "Invalid Id")); + fail("Expecting getForm for 'Invalid Kind/Item' to fail"); } catch (Exception e) { @@ -459,14 +791,22 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest } String missingNode = this.document.toString().replace("-", "x"); - Form form = this.formService.getForm(missingNode); - assertNull("Expecting getForm for a missing node to be null", form); + + try + { + this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, missingNode)); + fail("Expecting getForm for a missing node to fail"); + } + catch (FormNotFoundException fnne) + { + // expected + } // 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"); + this.formService.saveForm(new Item("Invalid Kind", "Invalid Id"), new FormData()); + fail("Expecting saveForm for 'Invalid Kind/Item' to fail"); } catch (Exception e) { @@ -475,7 +815,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest try { - this.formService.saveForm(missingNode, new FormData()); + this.formService.saveForm(new Item(NODE_FORM_ITEM_KIND, missingNode), new FormData()); fail("Expecting saveForm for a missing node to fail"); } catch (Exception e) @@ -488,7 +828,10 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest { Map model = new HashMap(); model.put("testDoc", this.document.toString()); + model.put("testDocName", this.documentName); model.put("testAssociatedDoc", this.associatedDoc.toString()); + model.put("folder", this.folder.toString()); + model.put("folderName", this.folderName); ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/forms/script/test_formService.js"); this.scriptService.executeScript(location, model); diff --git a/source/java/org/alfresco/repo/forms/Item.java b/source/java/org/alfresco/repo/forms/Item.java new file mode 100644 index 0000000000..27970bf0e4 --- /dev/null +++ b/source/java/org/alfresco/repo/forms/Item.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.forms; + +import org.alfresco.util.ParameterCheck; + +/** + * Represents an item a form is generated for. + * + * @author Gavin Cornwell + */ +public class Item +{ + private String kind; + private String id; + + /** + * Constructs an item. + * + * @param kind The kind of item, for example, 'node', 'task' + * @param id The identifier of the item + */ + public Item(String kind, String id) + { + ParameterCheck.mandatoryString("kind", kind); + ParameterCheck.mandatoryString("id", id); + + this.kind = kind; + this.id = id; + } + + /** + * Returns the kind of item. + * + * @return The kind of item + */ + public String getKind() + { + return this.kind; + } + + /** + * Returns the identifier of the item + * + * @return The identifier of the item + */ + public String getId() + { + return this.id; + } + + /* + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "[" + this.kind + "]" + this.id; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java index 23e7007b5c..e7d840925a 100644 --- a/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessor.java @@ -27,6 +27,7 @@ package org.alfresco.repo.forms.processor; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.alfresco.repo.forms.Item; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -114,14 +115,14 @@ public abstract class AbstractFormProcessor implements FormProcessor } /* - * @see org.alfresco.repo.forms.processor.FormProcessor#isApplicable(java.lang.String) + * @see org.alfresco.repo.forms.processor.FormProcessor#isApplicable(org.alfresco.repo.forms.Item) */ - public boolean isApplicable(String item) + public boolean isApplicable(Item item) { // this form processor matches if the match pattern provided matches - // the item provided + // the kind of the item provided - Matcher matcher = patternMatcher.matcher(item); + Matcher matcher = patternMatcher.matcher(item.getKind()); boolean matches = matcher.matches(); if (logger.isDebugEnabled()) diff --git a/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessorByHandlers.java b/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessorByHandlers.java index a660d5fefe..6b6ef084f7 100644 --- a/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessorByHandlers.java +++ b/source/java/org/alfresco/repo/forms/processor/AbstractFormProcessorByHandlers.java @@ -24,9 +24,12 @@ */ package org.alfresco.repo.forms.processor; +import java.util.List; + import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.FormData; import org.alfresco.repo.forms.FormException; +import org.alfresco.repo.forms.Item; /** * Abstract base class for all FormProcessor implementations that wish to use the @@ -52,11 +55,13 @@ public abstract class AbstractFormProcessorByHandlers extends AbstractFormProces * Generates a Form for the given item, constructed by calling each * applicable registered handler * - * @see org.alfresco.repo.forms.processor.FormProcessor#generate(java.lang.String) + * @see org.alfresco.repo.forms.processor.FormProcessor#generate(org.alfresco.repo.forms.Item, java.util.List, java.util.List) * @param item The item to generate a form for + * @param fields Restricted list of fields to include + * @param forcedFields List of fields to forcibly include * @return The generated Form */ - public Form generate(String item) + public Form generate(Item item, List fields, List forcedFields) { if (this.handlerRegistry == null) { @@ -72,7 +77,7 @@ public abstract class AbstractFormProcessorByHandlers extends AbstractFormProces // execute each applicable handler for (Handler handler: this.handlerRegistry.getApplicableHandlers(typedItem)) { - form = handler.handleGenerate(typedItem, form); + form = handler.handleGenerate(typedItem, fields, forcedFields, form); } return form; @@ -82,11 +87,11 @@ public abstract class AbstractFormProcessorByHandlers extends AbstractFormProces * Persists the given form data for the given item, completed by calling * each applicable registered handler * - * @see org.alfresco.repo.forms.processor.FormProcessor#persist(java.lang.String, org.alfresco.repo.forms.FormData) + * @see org.alfresco.repo.forms.processor.FormProcessor#persist(org.alfresco.repo.forms.Item, org.alfresco.repo.forms.FormData) * @param item The item to save the form for * @param data The object representing the form data */ - public void persist(String item, FormData data) + public void persist(Item item, FormData data) { if (this.handlerRegistry == null) { @@ -113,5 +118,5 @@ public abstract class AbstractFormProcessorByHandlers extends AbstractFormProces * @param item The item to get a typed object for * @return The typed object */ - protected abstract Object getTypedItem(String item); + protected abstract Object getTypedItem(Item item); } diff --git a/source/java/org/alfresco/repo/forms/processor/AbstractHandler.java b/source/java/org/alfresco/repo/forms/processor/AbstractHandler.java index 27aec60c34..1497f2af25 100644 --- a/source/java/org/alfresco/repo/forms/processor/AbstractHandler.java +++ b/source/java/org/alfresco/repo/forms/processor/AbstractHandler.java @@ -85,7 +85,7 @@ public abstract class AbstractHandler implements Handler } /* - * @see org.alfresco.repo.forms.processor.FormProcessorHandler#isApplicable(java.lang.String) + * @see org.alfresco.repo.forms.processor.Handler#isApplicable(java.lang.String) */ public boolean isApplicable(Object item) { diff --git a/source/java/org/alfresco/repo/forms/processor/FormProcessor.java b/source/java/org/alfresco/repo/forms/processor/FormProcessor.java index b991ac8ffa..d246398a01 100644 --- a/source/java/org/alfresco/repo/forms/processor/FormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/FormProcessor.java @@ -24,8 +24,11 @@ */ package org.alfresco.repo.forms.processor; +import java.util.List; + import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.FormData; +import org.alfresco.repo.forms.Item; /** * Interface definition of a form processor which is responsible @@ -44,7 +47,7 @@ public interface FormProcessor * @param item The item the form is being generated for * @return true if the processor is applicable */ - public boolean isApplicable(String item); + public boolean isApplicable(Item item); /** * Determines whether this form processor is active @@ -57,9 +60,16 @@ public interface FormProcessor * Returns a Form representation for an item * * @param item The item to generate a Form object for + * @param fields Restricted list of fields to include, null + * indicates all possible fields for the item + * should be included + * @param forcedFields List of field names from 'fields' list + * that should be forcibly included, it is + * up to the form processor implementation + * to determine how to enforce this * @return The Form representation */ - public Form generate(String item); + public Form generate(Item item, List fields, List forcedFields); /** * Persists the given object representing the form data @@ -68,5 +78,5 @@ public interface FormProcessor * @param item The item to generate a Form object for * @param data An object representing the data of the form */ - public void persist(String item, FormData data); + public void persist(Item item, FormData data); } diff --git a/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java b/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java index 508d4b43f0..946dc0ff12 100644 --- a/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java +++ b/source/java/org/alfresco/repo/forms/processor/FormProcessorRegistry.java @@ -27,6 +27,7 @@ package org.alfresco.repo.forms.processor; import java.util.ArrayList; import java.util.List; +import org.alfresco.repo.forms.Item; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -85,7 +86,7 @@ public class FormProcessorRegistry * @param item The item to find a form processor for * @return An applicable FormProcessor */ - public FormProcessor getApplicableFormProcessor(String item) + public FormProcessor getApplicableFormProcessor(Item item) { FormProcessor selectedProcessor = null; diff --git a/source/java/org/alfresco/repo/forms/processor/Handler.java b/source/java/org/alfresco/repo/forms/processor/Handler.java index fabaec18ca..091ebf2ccc 100644 --- a/source/java/org/alfresco/repo/forms/processor/Handler.java +++ b/source/java/org/alfresco/repo/forms/processor/Handler.java @@ -24,6 +24,8 @@ */ package org.alfresco.repo.forms.processor; +import java.util.List; + import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.FormData; @@ -35,8 +37,6 @@ import org.alfresco.repo.forms.FormData; */ public interface Handler { - // TODO: Investigate whether Generics can be used instead of Object - /** * Determines whether the handler is applicable for the given item. *

@@ -63,11 +63,14 @@ public interface Handler * to a more appropriate object, for example all the Node based handlers * can expect a NodeRef object and therefore cast to that. * + * @see org.alfresco.repo.forms.processor.FormProcessor#generate(org.alfresco.repo.forms.Item, java.util.List, java.util.List) * @param item The item to generate a Form for + * @param fields Restricted list of fields to include + * @param forcedFields List of fields to forcibly include * @param form The Form object * @return The modified Form object */ - public Form handleGenerate(Object item, Form form); + public Form handleGenerate(Object item, List fields, List forcedFields, Form form); /** * Handles the persistence of form data for the given item. diff --git a/source/java/org/alfresco/repo/forms/processor/NodeFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/NodeFormProcessor.java index 97ce3bedf1..2560d60388 100644 --- a/source/java/org/alfresco/repo/forms/processor/NodeFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/NodeFormProcessor.java @@ -24,9 +24,13 @@ */ package org.alfresco.repo.forms.processor; +import org.alfresco.repo.forms.FormNotFoundException; +import org.alfresco.repo.forms.Item; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * FormProcessor implementation that can generate and persist Form objects @@ -36,6 +40,9 @@ import org.alfresco.service.cmr.repository.NodeService; */ public class NodeFormProcessor extends AbstractFormProcessorByHandlers { + /** Logger */ + private static Log logger = LogFactory.getLog(NodeFormProcessor.class); + /** Services */ protected NodeService nodeService; @@ -50,18 +57,50 @@ public class NodeFormProcessor extends AbstractFormProcessorByHandlers } /* - * @see org.alfresco.repo.forms.processor.AbstractFormProcessor#getTypedItem(java.lang.String) + * @see org.alfresco.repo.forms.processor.AbstractFormProcessorByHandlers#getTypedItem(org.alfresco.repo.forms.Item) */ @Override - protected Object getTypedItem(String item) + protected Object getTypedItem(Item item) { - // create NodeRef representation - NodeRef nodeRef = new NodeRef(item); + // create NodeRef representation, the id could already be in a valid + // NodeRef format or it may be in a URL friendly format + NodeRef nodeRef = null; + if (NodeRef.isNodeRef(item.getId())) + { + nodeRef = new NodeRef(item.getId()); + } + else + { + // split the string into the 3 required parts + String[] parts = item.getId().split("/"); + if (parts.length == 3) + { + try + { + nodeRef = new NodeRef(parts[0], parts[1], parts[2]); + } + catch (IllegalArgumentException iae) + { + // ignored for now, dealt with below + + if (logger.isDebugEnabled()) + logger.debug("NodeRef creation failed for: " + item.getId(), iae); + } + } + } + + // check we have a valid node ref + if (nodeRef == null) + { + throw new FormNotFoundException(item, + new IllegalArgumentException(item.getId())); + } // check the node itself exists if (this.nodeService.exists(nodeRef) == false) { - throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); + throw new FormNotFoundException(item, + new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef)); } else { diff --git a/source/java/org/alfresco/repo/forms/processor/NodeHandler.java b/source/java/org/alfresco/repo/forms/processor/NodeHandler.java index 55c9ff2737..914cc583c0 100644 --- a/source/java/org/alfresco/repo/forms/processor/NodeHandler.java +++ b/source/java/org/alfresco/repo/forms/processor/NodeHandler.java @@ -26,7 +26,6 @@ package org.alfresco.repo.forms.processor; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -163,9 +162,9 @@ public class NodeHandler extends AbstractHandler } /* - * @see org.alfresco.repo.forms.processor.FormProcessorHandler#handleGenerate(java.lang.Object, org.alfresco.repo.forms.Form) + * @see org.alfresco.repo.forms.processor.Handler#handleGenerate(java.lang.Object, java.util.List, java.util.List, org.alfresco.repo.forms.Form) */ - public Form handleGenerate(Object item, Form form) + public Form handleGenerate(Object item, List fields, List forcedFields, Form form) { if (logger.isDebugEnabled()) logger.debug("Generating form for: " + item); @@ -174,7 +173,7 @@ public class NodeHandler extends AbstractHandler NodeRef nodeRef = (NodeRef)item; // generate the form for the node - generateNode(nodeRef, form); + generateNode(nodeRef, fields, forcedFields, form); if (logger.isDebugEnabled()) logger.debug("Returning form: " + form); @@ -182,201 +181,384 @@ public class NodeHandler extends AbstractHandler return form; } - /* - * @see org.alfresco.repo.forms.processor.FormProcessorHandler#handlePersist(java.lang.Object, org.alfresco.repo.forms.FormData) - */ - 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; - - // persist the node - persistNode(nodeRef, data); - } - /** * Sets up the Form object for the given NodeRef * * @param nodeRef The NodeRef to generate a Form for + * @param fields Restricted list of fields to include + * @param forcedFields List of fields to forcibly include * @param form The Form instance to populate */ - protected void generateNode(NodeRef nodeRef, Form form) + protected void generateNode(NodeRef nodeRef, List fields, List forcedFields, Form form) { // 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); - generateChildAssociationFields(nodeRef, form, formData); - generateTransientFields(nodeRef, form, formData); - form.setFormData(formData); + if (fields != null && fields.size() > 0) + { + generateSelectedFields(nodeRef, fields, forcedFields, form); + } + else + { + // setup field definitions and data + generateAllPropertyFields(nodeRef, form); + generateAllAssociationFields(nodeRef, form); + generateAllChildAssociationFields(nodeRef, form); + generateTransientFields(nodeRef, form); + } } /** - * Persists the given FormData on the given NodeRef + * Sets up the field definitions for all the requested fields. + * If any of the requested fields are not present on the node and they + * appear in the forcedFields list an attempt to find a model + * definition for those fields is made so they can be included. * - * @param nodeRef The NodeRef to persist the form data on - * @param data The FormData to persist + * @param nodeRef The NodeRef of the node being setup + * @param fields Restricted list of fields to include + * @param forcedFields List of field names that should be included + * even if the field is not currently present + * @param form The Form instance to populate */ - protected void persistNode(NodeRef nodeRef, FormData data) + protected void generateSelectedFields(NodeRef nodeRef, List fields, List forcedFields, Form form) { - // get the property definitions for the type of node being persisted + if (logger.isDebugEnabled()) + logger.debug("Generating selected fields: " + fields + " and forcing: " + forcedFields); + + // get data dictionary definition for node QName type = this.nodeService.getType(nodeRef); - Collection aspects = this.getAspectsToInclude(nodeRef); - TypeDefinition typeDef = this.dictionaryService.getAnonymousType(type, aspects); + TypeDefinition typeDef = this.dictionaryService.getAnonymousType(type, + this.nodeService.getAspects(nodeRef)); + Map propDefs = typeDef.getProperties(); Map assocDefs = typeDef.getAssociations(); Map childAssocDefs = typeDef.getChildAssociations(); - Map propDefs = typeDef.getProperties(); + Map propValues = this.nodeService.getProperties(nodeRef); - Map propsToPersist = new HashMap(data.getData().size()); - List assocsToPersist = new ArrayList(); - - for (String dataKey : data.getData().keySet()) + for (String fieldName : fields) { - FieldData fieldData = data.getData().get(dataKey); - // NOTE: ignore file fields for now, not supported yet! - if (fieldData.isFile() == false) + // try and split the field name + String[] parts = fieldName.split(":"); + if (parts.length == 2) { - String fieldName = fieldData.getName(); - - if (fieldName.startsWith(PROP_PREFIX)) + // create qname of field name + String qNamePrefix = parts[0]; + String localName = parts[1]; + QName fullQName = QName.createQName(qNamePrefix, localName, namespaceService); + + // lookup property def on node + PropertyDefinition propDef = propDefs.get(fullQName); + if (propDef != null) { - processPropertyPersist(nodeRef, propDefs, fieldData, propsToPersist); + // generate the property field + generatePropertyField(propDef, propValues.get(propDef.getName()), form); } - else if (fieldName.startsWith(ASSOC_PREFIX)) + else { - processAssociationPersist(nodeRef, assocDefs, childAssocDefs, fieldData, assocsToPersist); + // look for association defined for the type + AssociationDefinition assocDef = assocDefs.get(fullQName); + if (assocDef != null) + { + // generate the association field + generateAssociationField(assocDef, + this.nodeService.getTargetAssocs(nodeRef, fullQName), form); + } + else + { + ChildAssociationDefinition childAssocDef = childAssocDefs.get(fullQName); + if (childAssocDef != null) + { + // generate the association field + // TODO: see if we can get just the specific child assoc data + generateAssociationField(childAssocDef, + this.nodeService.getChildAssocs(nodeRef), form); + } + else + { + // still not found the field, is it a force'd field? + if (forcedFields != null && forcedFields.size() > 0 && + forcedFields.contains(fieldName)) + { + generateForcedField(fullQName, form); + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignoring field \"" + fieldName + + "\" as it is not defined for the current node and it does not appear in the 'force' list"); + } + } + } + } + } + else + { + // see if the fieldName is a well known transient property + if (TRANSIENT_MIMETYPE.equals(fieldName) || TRANSIENT_ENCODING.equals(fieldName) || + TRANSIENT_SIZE.equals(fieldName)) + { + // if the node type is content or sublcass thereof generate appropriate field + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + ContentData content = (ContentData)this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (content != null) + { + if (TRANSIENT_MIMETYPE.equals(fieldName)) + { + generateMimetypePropertyField(content, form); + } + else if (TRANSIENT_ENCODING.equals(fieldName)) + { + generateEncodingPropertyField(content, form); + } + else if (TRANSIENT_SIZE.equals(fieldName)) + { + generateSizePropertyField(content, form); + } + } + } } else if (logger.isWarnEnabled()) { - logger.warn("Ignoring unrecognised field '" + fieldName + "'"); + 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); - - for (AbstractAssocCommand cmd : assocsToPersist) - { - //TODO If there is an attempt to add and remove the same assoc in one request, - // we could drop each request and do nothing. - cmd.updateAssociations(nodeService); - } } /** - * Sets up the field definitions for the node's properties. + * Generates a field definition for the given field that is being forced + * to show + * + * @param fieldName QName of the field to force + * @param form The Form instance to populated + */ + protected void generateForcedField(QName fieldName, Form form) + { + if (logger.isDebugEnabled()) + logger.debug("Attempting to force the inclusion of field \"" + + fieldName.toPrefixString(this.namespaceService) + "\""); + + // lookup the field as a property in the model + PropertyDefinition propDef = this.dictionaryService.getProperty(fieldName); + if (propDef != null) + { + // generate the property field + generatePropertyField(propDef, null, form); + } + else + { + // lookup the field as an association in the model + AssociationDefinition assocDef = this.dictionaryService.getAssociation(fieldName); + if (assocDef != null) + { + // generate the association field + generateAssociationField(assocDef, null, form); + } + else if (logger.isWarnEnabled()) + { + logger.warn("Ignoring field \"" + fieldName.toPrefixString(this.namespaceService) + + "\" as it is not defined for the current node and can not be found in any model"); + } + } + } + + /** + * Sets up the field definitions for all 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") - protected void generatePropertyFields(NodeRef nodeRef, Form form, FormData formData) + protected void generateAllPropertyFields(NodeRef nodeRef, Form form) { // get data dictionary definition for node QName type = this.nodeService.getType(nodeRef); - Collection aspects = this.getAspectsToInclude(nodeRef); - TypeDefinition typeDef = this.dictionaryService.getAnonymousType(type, aspects); + TypeDefinition typeDef = this.dictionaryService.getAnonymousType(type, + this.nodeService.getAspects(nodeRef)); - // iterate round the property definitions, create the equivalent - // field definition and setup the data for the property + // iterate round the property definitions for the node and create + // the equivalent field definition and setup the data for the property Map propDefs = typeDef.getProperties(); Map propValues = this.nodeService.getProperties(nodeRef); for (PropertyDefinition propDef : propDefs.values()) { - String propName = propDef.getName().toPrefixString(this.namespaceService); - PropertyFieldDefinition fieldDef = new PropertyFieldDefinition( - propName, propDef.getDataType().getName().toPrefixString( - this.namespaceService)); + generatePropertyField(propDef, propValues.get(propDef.getName()), form); + } + } + + /** + * Sets up a field definition for the given property + * + * @param propDef The PropertyDefinition of the field to generate + * @param propValue The value of the property field + * @param form The Form instance to populate + */ + @SuppressWarnings("unchecked") + protected void generatePropertyField(PropertyDefinition propDef, Serializable propValue, Form form) + { + String propName = propDef.getName().toPrefixString(this.namespaceService); + PropertyFieldDefinition fieldDef = new PropertyFieldDefinition( + propName, propDef.getDataType().getName().toPrefixString( + this.namespaceService)); + + String title = propDef.getTitle(); + if (title == null) + { + title = propName; + } + fieldDef.setLabel(title); + fieldDef.setDefaultValue(propDef.getDefaultValue()); + fieldDef.setDescription(propDef.getDescription()); + fieldDef.setMandatory(propDef.isMandatory()); + fieldDef.setProtectedField(propDef.isProtected()); + fieldDef.setRepeating(propDef.isMultiValued()); + + // setup constraints for the property + List constraints = propDef.getConstraints(); + if (constraints != null && constraints.size() > 0) + { + List fieldConstraints = + new ArrayList(constraints.size()); - String title = propDef.getTitle(); - if (title == null) + for (ConstraintDefinition constraintDef : constraints) { - title = propName; - } - fieldDef.setLabel(title); - fieldDef.setDefaultValue(propDef.getDefaultValue()); - fieldDef.setDescription(propDef.getDescription()); - fieldDef.setMandatory(propDef.isMandatory()); - fieldDef.setProtectedField(propDef.isProtected()); - fieldDef.setRepeating(propDef.isMultiValued()); - - // setup constraints for the property - List constraints = propDef.getConstraints(); - if (constraints != null && constraints.size() > 0) - { - List fieldConstraints = - new ArrayList(constraints.size()); - - for (ConstraintDefinition constraintDef : constraints) + Constraint constraint = constraintDef.getConstraint(); + Map fieldConstraintParams = null; + Map constraintParams = constraint.getParameters(); + if (constraintParams != null) { - Constraint constraint = constraintDef.getConstraint(); - Map fieldConstraintParams = null; - Map constraintParams = constraint.getParameters(); - if (constraintParams != null) + // TODO: Just return the param value object, don't convert to String + fieldConstraintParams = new HashMap(constraintParams.size()); + for (String name : constraintParams.keySet()) { - // TODO: Just return the param value object, don't convert to String - fieldConstraintParams = new HashMap(constraintParams.size()); - for (String name : constraintParams.keySet()) + Object paramValue = constraintParams.get(name); + + if (paramValue instanceof List) { - Object paramValue = constraintParams.get(name); - - if (paramValue instanceof List) - { - paramValue = makeListString((List)paramValue); - } - - fieldConstraintParams.put(name, paramValue.toString()); + paramValue = makeListString((List)paramValue); } + + fieldConstraintParams.put(name, paramValue.toString()); } - FieldConstraint fieldConstraint = fieldDef.new FieldConstraint( - constraint.getType(), fieldConstraintParams); - fieldConstraints.add(fieldConstraint); } - - fieldDef.setConstraints(fieldConstraints); + FieldConstraint fieldConstraint = fieldDef.new FieldConstraint( + constraint.getType(), fieldConstraintParams); + fieldConstraints.add(fieldConstraint); } - form.addFieldDefinition(fieldDef); - - // get the field value and add to the form data object - Serializable fieldData = propValues.get(propDef.getName()); - if (fieldData != null) + fieldDef.setConstraints(fieldConstraints); + } + + form.addFieldDefinition(fieldDef); + + // add the property value to the form + if (propValue != null) + { + if (propValue instanceof List) { - if (fieldData instanceof List) - { - // temporarily add repeating field data as a comma - // separated list, this will be changed to using - // a separate field for each value once we have full - // UI support in place. - fieldData = makeListString((List)fieldData); - } + // temporarily add repeating field data as a comma + // separated list, this will be changed to using + // a separate field for each value once we have full + // UI support in place. + propValue = makeListString((List)propValue); + } + + form.addData(PROP_PREFIX + fieldDef.getName(), propValue); + } + } + + /** + * 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 + */ + protected void generateTransientFields(NodeRef nodeRef, Form form) + { + // 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 + generateMimetypePropertyField(content, form); - formData.addData(PROP_PREFIX + fieldDef.getName(), fieldData); + // setup encoding field + generateEncodingPropertyField(content, form); + + // setup size field + generateSizePropertyField(content, form); } } } /** - * Sets up the field definitions for the node's associations. + * Generates the field definition for the transient mimetype property + * + * @param content The ContentData object to generate the field from + * @param form The Form instance to populate + */ + protected void generateMimetypePropertyField(ContentData content, Form form) + { + 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); + form.addData(PROP_PREFIX + TRANSIENT_MIMETYPE, content.getMimetype()); + } + + /** + * Generates the field definition for the transient encoding property + * + * @param content The ContentData object to generate the field from + * @param form The Form instance to populate + */ + protected void generateEncodingPropertyField(ContentData content, Form form) + { + 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); + form.addData(PROP_PREFIX + TRANSIENT_ENCODING, content.getEncoding()); + } + + /** + * Generates the field definition for the transient size property + * + * @param content The ContentData object to generate the field from + * @param form The Form instance to populate + */ + protected void generateSizePropertyField(ContentData content, Form form) + { + 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); + form.addData(PROP_PREFIX + TRANSIENT_SIZE, new Long(content.getSize())); + } + + /** + * Sets up the field definitions for all 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) + protected void generateAllAssociationFields(NodeRef nodeRef, Form form) { + // ******************************************************** + // re-factor this to gen. all assocs defs and not values + // ******************************************************** + // add target association data List associations = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); @@ -431,11 +613,11 @@ public class NodeHandler extends AbstractHandler // add the value as a List (or add to the list if the form data // is already present) - FieldData fieldData = formData.getData().get(prefixedAssocName); + FieldData fieldData = form.getFormData().getData().get(prefixedAssocName); if (fieldData == null) { targets = new ArrayList(4); - formData.addData(prefixedAssocName, targets); + form.addData(prefixedAssocName, targets); } else { @@ -448,7 +630,7 @@ public class NodeHandler extends AbstractHandler else { // there should only be one value - formData.addData(prefixedAssocName, assocValue); + form.addData(prefixedAssocName, assocValue); } } } @@ -459,9 +641,8 @@ public class NodeHandler extends AbstractHandler * * @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 generateChildAssociationFields(NodeRef nodeRef, Form form, FormData formData) + protected void generateAllChildAssociationFields(NodeRef nodeRef, Form form) { List childAssocs = this.nodeService.getChildAssocs(nodeRef); if (childAssocs.isEmpty()) @@ -527,7 +708,7 @@ public class NodeHandler extends AbstractHandler // We don't want the whitespace or enclosing square brackets that come // with java.util.List.toString(), hence the custom String construction. - StringBuilder assocValue = new StringBuilder(); + /*StringBuilder assocValue = new StringBuilder(); for (Iterator iter = values.iterator(); iter.hasNext(); ) { ChildAssociationRef nextChild = iter.next(); @@ -537,55 +718,142 @@ public class NodeHandler extends AbstractHandler assocValue.append(","); } } - formData.addData(ASSOC_PREFIX + associationName, assocValue.toString()); + form.addData(ASSOC_PREFIX + associationName, assocValue.toString());*/ + + List nodeRefs = new ArrayList(4); + for (Iterator iter = values.iterator(); iter.hasNext(); ) + { + ChildAssociationRef nextChild = iter.next(); + nodeRefs.add(nextChild.getChildRef().toString()); + } + form.addData(ASSOC_PREFIX + associationName, nodeRefs); } } /** - * Sets up the field definitions for any transient fields that may be - * useful, for example, 'mimetype', 'size' and 'encoding'. + * Sets up a field definition for the given association * - * @param nodeRef The NodeRef of the node being setup + * @param assocDef The AssociationDefinition of the field to generate + * @param assocValues The values of the association field * @param form The Form instance to populate - * @param formData The FormData instance to populate */ - protected void generateTransientFields(NodeRef nodeRef, Form form, FormData formData) + @SuppressWarnings("unchecked") + protected void generateAssociationField(AssociationDefinition assocDef, + List assocValues, Form form) { - // 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)) + String assocName = assocDef.getName().toPrefixString(this.namespaceService); + AssociationFieldDefinition fieldDef = new AssociationFieldDefinition(assocName, + assocDef.getTargetClass().getName().toPrefixString( + this.namespaceService), Direction.TARGET); + String title = assocDef.getTitle(); + if (title == null) { - ContentData content = (ContentData)this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - if (content != null) + title = assocName; + } + fieldDef.setLabel(title); + fieldDef.setDescription(assocDef.getDescription()); + fieldDef.setProtectedField(assocDef.isProtected()); + fieldDef.setEndpointMandatory(assocDef.isTargetMandatory()); + fieldDef.setEndpointMany(assocDef.isTargetMany()); + + // add definition to the form + form.addFieldDefinition(fieldDef); + + // add the association value to the form + String prefixedAssocName = ASSOC_PREFIX + assocName; + + // determine the type of association values data and extract accordingly + List values = new ArrayList(4); + for (Object value : assocValues) + { + if (value instanceof ChildAssociationRef) { - // 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())); + values.add(((ChildAssociationRef)value).getChildRef().toString()); } + else if (value instanceof AssociationRef) + { + values.add(((AssociationRef)value).getTargetRef().toString()); + } + else + { + values.add(value.toString()); + } + } + + // Add the list as the value for the association. + // TODO: Do we also return a well known named list of association names + // for each noderef so that clients do not have extra work to do + // to display the current values to the user + form.addData(prefixedAssocName, values); + } + + /* + * @see org.alfresco.repo.forms.processor.FormProcessorHandler#handlePersist(java.lang.Object, org.alfresco.repo.forms.FormData) + */ + 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; + + // persist the node + persistNode(nodeRef, data); + } + + /** + * 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 assocDefs = typeDef.getAssociations(); + Map childAssocDefs = typeDef.getChildAssociations(); + Map propDefs = typeDef.getProperties(); + + Map propsToPersist = new HashMap(data.getData().size()); + List assocsToPersist = new ArrayList(); + + 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)) + { + processAssociationPersist(nodeRef, assocDefs, childAssocDefs, fieldData, assocsToPersist); + } + 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); + + for (AbstractAssocCommand cmd : assocsToPersist) + { + //TODO If there is an attempt to add and remove the same assoc in one request, + // we could drop each request and do nothing. + cmd.updateAssociations(nodeService); } } @@ -946,33 +1214,6 @@ public class NodeHandler extends AbstractHandler } } - /** - * Returns a Collection of aspects for the given noderef to use - * for generating and persisting the form. - * - * @param nodeRef The NodeRef of the node to get aspects for - * @return The Collection of aspects - */ - protected Collection getAspectsToInclude(NodeRef nodeRef) - { - List currentAspects = new ArrayList(); - currentAspects.addAll(this.nodeService.getAspects(nodeRef)); - - // TODO: make the list of aspects to ensure are present configurable - - // make sure the titled and author aspects are present - if (currentAspects.contains(ContentModel.ASPECT_TITLED) == false) - { - currentAspects.add(ContentModel.ASPECT_TITLED); - } - if (currentAspects.contains(ContentModel.ASPECT_AUTHOR) == false) - { - currentAspects.add(ContentModel.ASPECT_AUTHOR); - } - - return currentAspects; - } - /** * Returns a comma separated list string of the given list object. * diff --git a/source/java/org/alfresco/repo/forms/script/ScriptForm.java b/source/java/org/alfresco/repo/forms/script/ScriptForm.java index 6c175a316a..a600af641d 100644 --- a/source/java/org/alfresco/repo/forms/script/ScriptForm.java +++ b/source/java/org/alfresco/repo/forms/script/ScriptForm.java @@ -58,9 +58,14 @@ public class ScriptForm implements Serializable } } - public String getItem() + public String getItemKind() { - return form.getItem(); + return form.getItem().getKind(); + } + + public String getItemId() + { + return form.getItem().getId(); } public String getType() diff --git a/source/java/org/alfresco/repo/forms/script/ScriptFormService.java b/source/java/org/alfresco/repo/forms/script/ScriptFormService.java index 2d6a32b137..19d4d48f62 100644 --- a/source/java/org/alfresco/repo/forms/script/ScriptFormService.java +++ b/source/java/org/alfresco/repo/forms/script/ScriptFormService.java @@ -24,11 +24,14 @@ */ package org.alfresco.repo.forms.script; +import java.util.Arrays; +import java.util.List; + import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.FormData; import org.alfresco.repo.forms.FormService; +import org.alfresco.repo.forms.Item; import org.alfresco.repo.jscript.BaseScopableProcessorExtension; -import org.alfresco.service.ServiceRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,22 +43,10 @@ import org.apache.commons.logging.LogFactory; public class ScriptFormService extends BaseScopableProcessorExtension { private static Log logger = LogFactory.getLog(ScriptFormService.class); - /** Service Registry */ - private ServiceRegistry serviceRegistry; - + /** The site service */ private FormService formService; - /** - * Sets the Service Registry - * - * @param serviceRegistry - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - /** * Set the form service * @@ -68,25 +59,79 @@ public class ScriptFormService extends BaseScopableProcessorExtension } /** - * Returns the form for the given item + * Returns a form representation of the given item, + * all known fields for the item are included. * - * @param item The item to retrieve a form for + * @param itemKind The kind of item to retrieve a form for + * @param itemId The identifier of the item to retrieve a form for * @return The form */ - public ScriptForm getForm(String item) + public ScriptForm getForm(String itemKind, String itemId) { - Form result = formService.getForm(item); + return getForm(itemKind, itemId, null); + } + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param itemKind The kind of item to retrieve a form for + * @param itemId The identifier of the item to retrieve a form for + * @param fields String array of fields to include, null + * indicates all possible fields for the item + * should be included + * @return The form + */ + public ScriptForm getForm(String itemKind, String itemId, String[] fields) + { + return getForm(itemKind, itemId, fields, null); + } + + /** + * Returns a form representation of the given item consisting + * only of the given fields. + * + * @param itemKind The kind of item to retrieve a form for + * @param itemId The identifier of the item to retrieve a form for + * @param fields String array of fields to include, null + * indicates all possible fields for the item + * should be included + * @param forcedFields List of field names from 'fields' list + * that should be forcibly included, it is + * up to the form processor implementation + * to determine how to enforce this + * @return The form + */ + public ScriptForm getForm(String itemKind, String itemId, + String[] fields, String[] forcedFields) + { + // create List representations of field params if necessary + List fieldsList = null; + List forcedFieldsList = null; + + if (fields != null) + { + fieldsList = Arrays.asList(fields); + } + + if (forcedFields != null) + { + forcedFieldsList = Arrays.asList(forcedFields); + } + + Form result = formService.getForm(new Item(itemKind, itemId), fieldsList, forcedFieldsList); return result == null ? null : new ScriptForm(result); } /** * Persists the given data object for the item provided * - * @param item The item to persist the data for + * @param itemKind The kind of item to retrieve a form for + * @param itemId The identifier of the item to retrieve a form for * @param postData The post data, this can be a Map of name value * pairs, a webscript FormData object or a JSONObject */ - public void saveForm(String item, Object postData) + public void saveForm(String itemKind, String itemId, Object postData) { // A note on data conversion as passed in to this method: // Each of the 3 submission methods (multipart/formdata, JSON Post and @@ -109,6 +154,6 @@ public class ScriptFormService extends BaseScopableProcessorExtension return; } - formService.saveForm(item, dataForFormService); + formService.saveForm(new Item(itemKind, itemId), dataForFormService); } } 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 99044bcb2d..3e2107470b 100644 --- a/source/java/org/alfresco/repo/forms/script/test_formService.js +++ b/source/java/org/alfresco/repo/forms/script/test_formService.js @@ -3,37 +3,48 @@ function testGetFormForNonExistentContentNode() // Replace all the digits in the ID with an 'x'. // Surely that node will not exist... var corruptedTestDoc = testDoc.replace(/\d/g, "x"); - var form = formService.getForm(corruptedTestDoc); + var form = null; + + try + { + form = formService.getForm("node", corruptedTestDoc); + } + catch (e) + { + // expected + } + test.assertNull(form, "Form should have not been found: " + testDoc); } function testGetFormForContentNode() { - // Get a known form and check its various attributes/properties. - var form = formService.getForm(testDoc); - test.assertNotNull(form, "Form should have been found: " + testDoc); - - test.assertEquals(testDoc, form.item); - test.assertEquals('cm:content', form.type); - - test.assertNull(form.fieldGroups, "form.fieldGroups should be null."); - - var fieldDefs = form.fieldDefinitions; - test.assertNotNull(fieldDefs, "field definitions should not be null."); - test.assertEquals(23, fieldDefs.length); - - // as we know there are no duplicates we can safely create a map of the - // field definitions for the purposes of this test - var fieldDefnDataHash = {}; - var fieldDef = null; - for (var x = 0; x < fieldDefs.length; x++) - { - fieldDef = fieldDefs[x]; - fieldDefnDataHash[fieldDef.name] = fieldDef; - } - - var nameField = fieldDefnDataHash['cm:name']; - var titleField = fieldDefnDataHash['cm:title']; + // Get a known form and check its various attributes/properties. + var form = formService.getForm("node", testDoc); + test.assertNotNull(form, "Form should have been found for: " + testDoc); + + test.assertEquals("node", form.itemKind); + test.assertEquals(testDoc, form.itemId); + test.assertEquals('cm:content', form.type); + + test.assertNull(form.fieldGroups, "form.fieldGroups should be null."); + + var fieldDefs = form.fieldDefinitions; + test.assertNotNull(fieldDefs, "field definitions should not be null."); + test.assertEquals(22, fieldDefs.length); + + // as we know there are no duplicates we can safely create a map of the + // field definitions for the purposes of this test + var fieldDefnDataHash = {}; + var fieldDef = null; + for (var x = 0; x < fieldDefs.length; x++) + { + fieldDef = fieldDefs[x]; + fieldDefnDataHash[fieldDef.name] = fieldDef; + } + + var nameField = fieldDefnDataHash['cm:name']; + var titleField = fieldDefnDataHash['cm:title']; var descField = fieldDefnDataHash['cm:description']; var originatorField = fieldDefnDataHash['cm:originator']; var addresseeField = fieldDefnDataHash['cm:addressee']; @@ -101,6 +112,7 @@ function testGetFormForContentNode() test.assertNotNull(fieldData, "fieldData should not be null."); test.assertNotNull(fieldData.length, "fieldData.length should not be null."); + test.assertEquals(testDocName, fieldData["prop:cm:name"].value); test.assertEquals("This is the title for the test document", fieldData["prop:cm:title"].value); test.assertEquals("This is the description for the test document", fieldData["prop:cm:description"].value); test.assertEquals("fred@customer.com", fieldData["prop:cm:originator"].value); @@ -126,6 +138,195 @@ function testGetFormForContentNode() test.assertEquals(testAssociatedDoc, targets); } +function testGetFormForFolderNode() +{ + // Get a known form and check its various attributes/properties. + var form = formService.getForm("node", folder); + test.assertNotNull(form, "Form should have been found for: " + testDoc); + + test.assertEquals("node", form.itemKind); + test.assertEquals(folder, form.itemId); + test.assertEquals('cm:folder', form.type); + + test.assertNull(form.fieldGroups, "form.fieldGroups should be null."); + + var fieldDefs = form.fieldDefinitions; + test.assertNotNull(fieldDefs, "field definitions should not be null."); + test.assertEquals(11, fieldDefs.length); + + // as we know there are no duplicates we can safely create a map of the + // field definitions for the purposes of this test + var fieldDefnDataHash = {}; + var fieldDef = null; + for (var x = 0; x < fieldDefs.length; x++) + { + fieldDef = fieldDefs[x]; + fieldDefnDataHash[fieldDef.name] = fieldDef; + } + + var nameField = fieldDefnDataHash['cm:name']; + var containsField = fieldDefnDataHash['cm:contains']; + + test.assertNotNull(nameField, "Expecting to find the cm:name field"); + test.assertNotNull(containsField, "Expecting to find the cm:contains field"); + + // check the labels of all the fields + test.assertEquals("Name", nameField.label); + test.assertEquals("Contains", containsField.label); + + // check details of name field + test.assertEquals("d:text", nameField.dataType); + test.assertTrue(nameField.mandatory); + // Expecting cm:name to be single-valued. + test.assertFalse(nameField.repeating, "nameField.repeating was not false."); + + // compare the association details + test.assertEquals("TARGET", "" + containsField.endpointDirection); + test.assertFalse(containsField.endpointMandatory, "containsField.endpointMandatory was not false."); + test.assertTrue(containsField.endpointMany, "constainsField.endpointMany was not true."); + + // check the form data + var formData = form.formData; + test.assertNotNull(formData, "formData should not be null."); + var fieldData = formData.data; + test.assertNotNull(fieldData, "fieldData should not be null."); + test.assertNotNull(fieldData.length, "fieldData.length should not be null."); + + test.assertEquals(folderName, fieldData["prop:cm:name"].value); + var children = fieldData["assoc:cm:contains"].value; + test.assertNotNull(children, "children should not be null."); + var childrenArr = children.split(","); + test.assertTrue(childrenArr.length == 3, "Expecting there to be 3 children"); +} + +function testGetFormWithSelectedFields() +{ + // Define list of fields to retrieve + var fields = []; + fields.push("cm:name"); + fields.push("cm:title"); + fields.push("mimetype"); + fields.push("cm:modified"); + // add field that will be missing + fields.push("cm:author"); + + // Get a a form for the given fields + var form = formService.getForm("node", testDoc, fields); + test.assertNotNull(form, "Form should have been found for: " + testDoc); + + var fieldDefs = form.fieldDefinitions; + test.assertNotNull(fieldDefs, "field definitions should not be null."); + test.assertEquals(4, fieldDefs.length); + + // as we know there are no duplicates we can safely create a map of the + // field definitions for the purposes of this test + var fieldDefnDataHash = {}; + var fieldDef = null; + for (var x = 0; x < fieldDefs.length; x++) + { + fieldDef = fieldDefs[x]; + fieldDefnDataHash[fieldDef.name] = fieldDef; + } + + var nameField = fieldDefnDataHash['cm:name']; + var titleField = fieldDefnDataHash['cm:title']; + var mimetypeField = fieldDefnDataHash['mimetype']; + var modifiedField = fieldDefnDataHash['cm:modified']; + var authorField = fieldDefnDataHash['cm:author']; + + test.assertNotNull(nameField, "Expecting to find the cm:name field"); + test.assertNotNull(titleField, "Expecting to find the cm:title field"); + test.assertNotNull(mimetypeField, "Expecting to find the mimetype field"); + test.assertNotNull(modifiedField, "Expecting to find the cm:modified field"); + test.assertTrue(authorField === undefined, "Expecting cm:author field to be missing"); + + // check the labels of all the fields + test.assertEquals("Name", nameField.label); + test.assertEquals("Title", titleField.label); + test.assertEquals("Mimetype", mimetypeField.label); + test.assertEquals("Modified Date", modifiedField.label); + + // check the form data + var formData = form.formData; + test.assertNotNull(formData, "formData should not be null."); + var fieldData = formData.data; + test.assertNotNull(fieldData, "fieldData should not be null."); + test.assertNotNull(fieldData.length, "fieldData.length should not be null."); + + test.assertEquals(testDocName, fieldData["prop:cm:name"].value); + test.assertEquals("This is the title for the test document", fieldData["prop:cm:title"].value); +} + +function testGetFormWithForcedFields() +{ + // Define list of fields to retrieve + var fields = []; + fields.push("cm:name"); + fields.push("cm:title"); + fields.push("mimetype"); + fields.push("cm:modified"); + fields.push("cm:author"); + fields.push("cm:wrong"); + + // define a list of fields to force + var force = []; + force.push("cm:author"); + force.push("cm:wrong"); + + // Get a a form for the given fields + var form = formService.getForm("node", testDoc, fields, force); + test.assertNotNull(form, "Form should have been found for: " + testDoc); + + var fieldDefs = form.fieldDefinitions; + test.assertNotNull(fieldDefs, "field definitions should not be null."); + test.assertEquals(5, fieldDefs.length); + + // as we know there are no duplicates we can safely create a map of the + // field definitions for the purposes of this test + var fieldDefnDataHash = {}; + var fieldDef = null; + for (var x = 0; x < fieldDefs.length; x++) + { + fieldDef = fieldDefs[x]; + fieldDefnDataHash[fieldDef.name] = fieldDef; + } + + var nameField = fieldDefnDataHash['cm:name']; + var titleField = fieldDefnDataHash['cm:title']; + var mimetypeField = fieldDefnDataHash['mimetype']; + var modifiedField = fieldDefnDataHash['cm:modified']; + var authorField = fieldDefnDataHash['cm:author']; + var wrongField = fieldDefnDataHash['cm:wrong']; + + test.assertNotNull(nameField, "Expecting to find the cm:name field"); + test.assertNotNull(titleField, "Expecting to find the cm:title field"); + test.assertNotNull(mimetypeField, "Expecting to find the mimetype field"); + test.assertNotNull(modifiedField, "Expecting to find the cm:modified field"); + test.assertNotNull(authorField, "Expecting to find the cm:author field"); + test.assertTrue(wrongField === undefined, "Expecting cm:wrong field to be missing"); + + // check the labels of all the fields + test.assertEquals("Name", nameField.label); + test.assertEquals("Title", titleField.label); + test.assertEquals("Mimetype", mimetypeField.label); + test.assertEquals("Modified Date", modifiedField.label); + test.assertEquals("Author", authorField.label); + + // check the form data + var formData = form.formData; + test.assertNotNull(formData, "formData should not be null."); + var fieldData = formData.data; + test.assertNotNull(fieldData, "fieldData should not be null."); + test.assertNotNull(fieldData.length, "fieldData.length should not be null."); + + test.assertEquals(testDocName, fieldData["prop:cm:name"].value); + test.assertEquals("This is the title for the test document", fieldData["prop:cm:title"].value); + test.assertNull(fieldData["prop:cm:author"], "Expecting cm:author to be null"); +} + // Execute tests testGetFormForNonExistentContentNode(); -testGetFormForContentNode(); \ No newline at end of file +testGetFormForContentNode(); +testGetFormForFolderNode(); +testGetFormWithSelectedFields(); +testGetFormWithForcedFields();