FormService checkpoint - Configurable, extendable form processor mechanism introduced, default handler for building repo node based forms implemented and the unit test actually tests stuff now!

This is just the Java service API, there is no script or REST API yet.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12342 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2008-12-10 13:06:38 +00:00
parent d24b54b4e6
commit c159cdbe90
20 changed files with 2003 additions and 263 deletions

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2005-2008 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;
/**
* An association field definition that can represent a source->target association
* or a target->source association.
*
* @author Gavin Cornwell
*/
public class AssociationFieldDefinition extends FieldDefinition
{
public enum Direction { SOURCE, TARGET }
protected String endpointType;
protected Direction endpointDirection;
protected boolean endpointMandatory = false;
protected boolean endpointMany = false;
/**
* Default constructor
*
* @param name The name of the association
* @param endpointType The type of the item at the end of the association
* @param endpointDirection The direction the association is going
*/
public AssociationFieldDefinition(String name, String endpointType, Direction endpointDirection)
{
super(name);
this.endpointType = endpointType;
this.endpointDirection = endpointDirection;
}
/**
* Returns the type of the target of the association
*
* @return The type of the target
*/
public String getEndpointType()
{
return this.endpointType;
}
/**
* Returns the direction the association is going.
* <p>
* <code>Direction.TARGET</code> means the endpoint is the target
* and the field is the source.
* <p>
* <code>Direction.SOURCE</code> means the endpoint is the source
* and the field is the target.
*
* @return Direction.TARGET or Direction.SOURCE
*/
public Direction getEnpointDirection()
{
return this.endpointDirection;
}
/**
* Determines whether the target is mandatory
*
* @return true if a target has to be selected
*/
public boolean isEndpointMandatory()
{
return this.endpointMandatory;
}
/**
* Sets whether the target is mandatory
*
* @param endpointMandatory true if a target has to be selected
*/
public void setEndpointMandatory(boolean endpointMandatory)
{
this.endpointMandatory = endpointMandatory;
}
/**
* Determines if multiple targets can be selected
*
* @return true if multiple targets can be selected
*/
public boolean isEndpointMany()
{
return this.endpointMany;
}
/**
* Sets whether multiple targets can be selected
*
* @param targetMany true if multiple targets can be selected
*/
public void setEndpointMany(boolean endpointMany)
{
this.endpointMany = endpointMany;
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("name=").append(this.name);
buffer.append(", endpointType=").append(this.endpointType);
buffer.append(", endpointDirection=").append(this.endpointDirection);
buffer.append(", endpointMandatory=").append(this.endpointMandatory);
buffer.append(", endpointMany=").append(this.endpointMany);
buffer.append(", label=").append(this.label);
buffer.append(", description=").append(this.description);
buffer.append(", binding=").append(this.binding);
buffer.append(", defaultValue=").append(this.defaultValue);
buffer.append(", group=").append(this.group);
buffer.append(", protectedField=").append(this.protectedField);
buffer.append(")");
return buffer.toString();
}
}

View File

@@ -1,128 +0,0 @@
/*
* Copyright (C) 2005-2008 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;
/**
* An association field definition.
*
* @author Gavin Cornwell
*/
public class AssociationFieldDefiniton extends FieldDefinition
{
//
// TODO: Think about bi-directional association support,
// should we instead model as endpointType etc.
// and have a 'direction' flag, that would then allow
// form clients to display source objects not just
// target objects
//
protected String targetType;
protected String targetRole;
protected boolean targetMandatory;
protected boolean targetMany;
// TODO: This may not even be needed for forms, what difference does it make!?
protected boolean childAssociation;
/**
* Constructs an AssociationFieldDefinition
*
* @param name The name of the property
* @param label The display label of the property
* @param description The description of the property
* @param dataType The data type of the property
* @param defaultValue Default value of the property
* @param binding Binding of the property
* @param protectedField Whether the property should be read only
* @param mandatory Whether the property is mandatory
* @param repeats Whether the property can contain multiple values
* @param group The group the property belongs to
* @param targetMandatory Whether a target is mandatory
* @param targetMany Whether there can be multiple targets
* @param targetType The type of the target
* @param childAssociation Whether the association is a child association
*/
// TODO: Look at the Builder pattern to reduce the size of the constructor!!
public AssociationFieldDefiniton(String name, String label, String description,
String defaultValue, String binding, boolean protectedField,
FieldGroup group, boolean targetMandatory, boolean targetMany,
String targetRole, String targetType, boolean childAssociation)
{
this.name = name;
this.label = label;
this.description = description;
this.defaultValue = defaultValue;
this.binding = binding;
this.protectedField = protectedField;
this.group = group;
this.targetMandatory = targetMandatory;
this.targetMany = targetMany;
this.targetType = targetType;
this.childAssociation = childAssociation;
}
/**
* Returns the type of the target of the association
*
* @return The type of the target
*/
public String getTargetType()
{
return this.targetType;
}
/**
* Determines whether the target is mandatory
*
* @return true if a target has to be selected
*/
public boolean isTargetMandatory()
{
return this.targetMandatory;
}
/**
* Determines if multiple targets can be selected
*
* @return true if multiple targets can be selected
*/
public boolean isTargetMany()
{
return this.targetMany;
}
/**
* Determines if the association is a child association
*
* @return true if the association is a child association
*/
public boolean isChildAssociation()
{
return this.childAssociation;
}
}

View File

@@ -39,6 +39,14 @@ public abstract class FieldDefinition
protected FieldGroup group;
protected boolean protectedField;
/**
* Default constructor
*/
public FieldDefinition(String name)
{
this.name = name;
}
/**
* Returns the name of the field
*
@@ -59,6 +67,16 @@ public abstract class FieldDefinition
return this.label;
}
/**
* Sets the display label for the field
*
* @param label The field's display label
*/
public void setLabel(String label)
{
this.label = label;
}
/**
* Returns the description of the field
*
@@ -69,6 +87,16 @@ public abstract class FieldDefinition
return this.description;
}
/**
* Sets the description of the field
*
* @param description The field's description
*/
public void setDescription(String description)
{
this.description = description;
}
/**
* Returns the binding for the field, this is used by some
* FormModelProcessor implementations to generate an
@@ -81,6 +109,18 @@ public abstract class FieldDefinition
return this.binding;
}
/**
* Sets the binding to use for the field, this is used by some
* FormModelProcessor implementations to generate an
* alternative representation of the data
*
* @param binding The field's binding
*/
public void setBinding(String binding)
{
this.binding = binding;
}
/**
* Returns any default value the field may have
*
@@ -91,6 +131,16 @@ public abstract class FieldDefinition
return this.defaultValue;
}
/**
* Sets the default value for the field
*
* @param defaultValue The field's default value
*/
public void setDefaultValue(String defaultValue)
{
this.defaultValue = defaultValue;
}
/**
* Returns the group the field may be a part of
*
@@ -101,6 +151,16 @@ public abstract class FieldDefinition
return this.group;
}
/**
* Sets the group the field is part of
*
* @param group The group the field belongs to
*/
public void setGroup(FieldGroup group)
{
this.group = group;
}
/**
* Determines whether the field is protected i.e. it should be rendered
* as read-only in any client displaying the field
@@ -111,4 +171,15 @@ public abstract class FieldDefinition
{
return this.protectedField;
}
/**
* Sets whether the field is protected i.e. it should be rendered
* as read-only in any client displaying the field
*
* @param protectedField true if the field is protected
*/
public void setProtectedField(boolean protectedField)
{
this.protectedField = protectedField;
}
}

View File

@@ -24,8 +24,8 @@
*/
package org.alfresco.repo.forms;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collection;
/**
* Data representation of a form to be displayed in the UI.
@@ -35,32 +35,19 @@ import java.util.Map;
public class Form
{
protected String item;
protected String submissionUrl;
protected String type;
protected List<FieldDefinition> fieldDefinitions;
protected List<FieldGroup> fieldGroups;
protected Map<String, String> formData;
protected Collection<FieldDefinition> fieldDefinitions;
protected Collection<FieldGroup> fieldGroups;
protected FormData data;
/**
* Constructs a Form
*
* @param item The identifier
* @param submissionUrl Default submission URL
* @param type The type of the item
* @param fieldDefinitions List of fields that could be displayed
* @param fieldGroups List of field groups
* @param formData Map of the form data
* @param item An identifier for the item the form is for
*/
public Form(String item, String submissionUrl, String type,
List<FieldDefinition> fieldDefinitions, List<FieldGroup> fieldGroups,
Map<String, String> formData)
public Form(String item)
{
this.fieldDefinitions = fieldDefinitions;
this.fieldGroups = fieldGroups;
this.formData = formData;
this.item = item;
this.submissionUrl = submissionUrl;
this.type = type;
}
/**
@@ -72,18 +59,7 @@ public class Form
public String getItem()
{
return this.item;
}
/**
* Returns the submission URL to use to post back to this service, acts as a
* default URL for clients to use
*
* @return The default submission URL
*/
public String getSubmissionUrl()
{
return this.submissionUrl;
}
}
/**
* Returns the type of the item the form is for, could be a content model type, a
@@ -95,36 +71,114 @@ public class Form
{
return this.type;
}
/**
* Sets the type of the item this Form represents
*
* @param type The type
*/
public void setType(String type)
{
this.type = type;
}
/**
* Returns the list of fields appropriate for the item
* Returns the collection of field definitions for the form
*
* @return List of FieldDefintion objects or null if there are no fields
* @return Collection of FieldDefintion objects or null if there are no fields
*/
public List<FieldDefinition> getFieldDefinitions()
public Collection<FieldDefinition> getFieldDefinitions()
{
return this.fieldDefinitions;
}
/**
* Returns the list of field groups for the form
* Sets the collection of FieldDefintion objects representing the fields the
* form is able to display
*
* @return List of FieldGroup objects or null if there are no groups
* @param fieldDefinitions Collection of FieldDefinition objects
*/
public List<FieldGroup> getFieldGroups()
public void setFieldDefinitions(Collection<FieldDefinition> fieldDefinitions)
{
this.fieldDefinitions = fieldDefinitions;
}
/**
* Adds the given FieldDefinition to the form.
* <p>
* NOTE: Multiple fields with the same name can be added to the list,
* it is therefore the form processor and the client of the
* FormService responsibility to differentiate the fields in
* some way i.e. by type, property vs. association.
*
* @param definition The FieldDefinition to add
*/
public void addFieldDefinition(FieldDefinition definition)
{
if (this.fieldDefinitions == null)
{
this.fieldDefinitions = new ArrayList<FieldDefinition>(8);
}
this.fieldDefinitions.add(definition);
}
/**
* Returns the collection of field groups for the form
*
* @return Collection of FieldGroup objects or null if there are no groups
*/
public Collection<FieldGroup> getFieldGroups()
{
return this.fieldGroups;
}
/**
* Sets the collection of FieldGroup objects representing the groups of
* fields the form should display and maintain
*
* @param fieldGroups Collection of FieldGroup objects
*/
public void setFieldGroups(Collection<FieldGroup> fieldGroups)
{
this.fieldGroups = fieldGroups;
}
/**
* Returns the data to display in the form
*
* @return Map of String objects representing the form data or null if
* there is no data
* @return FormData object holding the data of the form or null
* if there is no data i.e. for a create form
*/
public Map<String, String> getFormData()
public FormData getFormData()
{
return this.formData;
return this.data;
}
/**
* Sets the data this form should display
*
* @param data FormData instance containing the data
*/
public void setFormData(FormData data)
{
this.data = data;
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("item=").append(this.item);
buffer.append(", type=").append(this.type);
buffer.append(", fieldGroups=").append(this.fieldGroups);
buffer.append("\nfieldDefinitions=").append(this.fieldDefinitions);
buffer.append("\nformData=").append(this.data);
buffer.append(")");
return buffer.toString();
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2005-2008 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 java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.NotImplementedException;
/**
* Represents the data going to or coming from a Form.
*
* @author Gavin Cornwell
*/
public class FormData
{
protected Map<String, FieldData> data;
/**
* Default constructor
*/
public FormData()
{
this.data = new HashMap<String, FieldData>(8);
}
/**
* Returns the data
*
* @return Map of DataItem objects representing the data
*/
public Map<String, FieldData> getData()
{
return this.data;
}
/**
* Sets the form data
*
* @param data Map of DataItem objects representing the data
*/
public void setData(Map<String, FieldData> data)
{
this.data = data;
}
/**
* Adds the given data to the form
*
* @param name The name of the data
* @param value The value of the data
*/
public void addData(String name, Object value)
{
FieldData item = new FieldData(name, value, false);
this.data.put(name, item);
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("data=").append(this.data);
buffer.append(")");
return buffer.toString();
}
/**
* Inner class to represent the value of a field on a form
*
* @author Gavin Cornwell
*/
public class FieldData
{
protected String name;
protected Object value;
protected boolean isFile = false;
/**
* Default Constructor
*
* @param name The name of the form field
* @param value The value of the form field
* @param isFile Whether the field data represents an uploaded file
*/
public FieldData(String name, Object value, boolean isFile)
{
this.name = name;
this.value = value;
this.isFile = isFile;
}
/**
* Returns the name of the form field that data represents
*
* @return The name
*/
public String getName()
{
return this.name;
}
/**
* Returns the value of the form field that data represents
*
* @return The value
*/
public Object getValue()
{
return this.value;
}
/**
* Determines whether the data represents a file
*
* @return true if the data is a file
*/
public boolean isFile()
{
return this.isFile;
}
/**
* Returns an InputStream onto the content of the file,
* throws IllegalStateException if this is called for
* non file field data
*
* @return An InputStream onto the file
*/
public InputStream getInputStream()
{
// TODO: implement this
throw new NotImplementedException();
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("name=").append(this.name);
buffer.append(", value=").append(this.value);
buffer.append(", isFile=").append(this.isFile);
buffer.append(")");
return buffer.toString();
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2005-2008 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 services
*
* @author Gavin Cornwell
*/
public class FormException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = 688834574410335422L;
public FormException(String msg)
{
super(msg);
}
public FormException(String msg, Throwable cause)
{
super(msg, cause);
}
}

View File

@@ -26,7 +26,7 @@ package org.alfresco.repo.forms;
/**
* Form service fundamental API.
* Form service API.
* <p>
* This service API is designed to support the public facing Form APIs
*
@@ -35,18 +35,18 @@ package org.alfresco.repo.forms;
public interface FormService
{
/**
* Returns a form representation of the item with the given id
* Returns a form representation of the given item
*
* @param id The id of the item to get a form for
* @param item The item to get a form for
* @return The Form representation
*/
public Form getForm(String id);
public Form getForm(String item);
/**
* Persists the given form representation for the given id
* Persists the given form representation for the given item
*
* @param id The id of the item to persist the form for
* @param form The Form representation
* @param item The item to persist the form for
* @param data An object representing the form data to persist
*/
public void saveForm(String id, Form form);
public void saveForm(String item, FormData data);
}

View File

@@ -24,10 +24,8 @@
*/
package org.alfresco.repo.forms;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.repo.forms.processor.FormProcessor;
import org.alfresco.repo.forms.processor.FormProcessorRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -42,68 +40,65 @@ public class FormServiceImpl implements FormService
private static Log logger = LogFactory.getLog(FormServiceImpl.class);
/** Services */
private NodeService nodeService;
private FileFolderService fileFolderService;
private SearchService searchService;
private PermissionService permissionService;
private FormProcessorRegistry processorRegistry;
/**
* Set node service
* Sets the FormProcessorRegistry
*
* @param nodeService node service
* @param registry The FormProcessorRegistry instance to use
*/
public void setNodeService(NodeService nodeService)
public void setProcessorRegistry(FormProcessorRegistry registry)
{
this.nodeService = nodeService;
this.processorRegistry = registry;
}
/**
* Set file folder service
*
* @param fileFolderService file folder service
*/
public void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
/**
* Set search service
*
* @param searchService search service
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Set permission service
*
* @param permissionService permission service
*/
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
/**
* @see org.alfresco.repo.forms.FormService#getForm(java.lang.String)
*/
public Form getForm(String id)
public Form getForm(String item)
{
if (logger.isDebugEnabled())
logger.debug("Retrieving form for item with id: " + id);
if (this.processorRegistry == null)
{
throw new FormException("Property 'processorRegistry' has not been set.");
}
return null;
if (logger.isDebugEnabled())
logger.debug("Retrieving form for item: " + item);
FormProcessor processor = this.processorRegistry.getApplicableFormProcessor(item);
if (processor == null)
{
throw new FormException("Failed to find appropriate FormProcessor to generate Form for item: " + item);
}
else
{
return processor.generate(item);
}
}
/*
* @see org.alfresco.repo.forms.FormService#saveForm(java.lang.String, org.alfresco.repo.forms.Form)
* @see org.alfresco.repo.forms.FormService#saveForm(java.lang.String, org.alfresco.repo.forms.FormData)
*/
public void saveForm(String id, Form form)
public void saveForm(String item, FormData data)
{
if (this.processorRegistry == null)
{
throw new FormException("FormProcessorRegistry has not been setup");
}
if (logger.isDebugEnabled())
logger.debug("Saving form for item with id '" + id + "': " + form);
logger.debug("Saving form for item '" + item + "': " + data);
FormProcessor processor = this.processorRegistry.getApplicableFormProcessor(item);
if (processor == null)
{
throw new FormException("Failed to find appropriate FormProcessor to persist Form for item: " + item);
}
else
{
processor.persist(item, data);
}
}
}

View File

@@ -24,7 +24,24 @@
*/
package org.alfresco.repo.forms;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.forms.AssociationFieldDefinition.Direction;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.GUID;
/**
* Form service implementation unit test.
@@ -34,6 +51,28 @@ import org.alfresco.util.BaseAlfrescoSpringTest;
public class FormServiceImplTest extends BaseAlfrescoSpringTest
{
private FormService formService;
private NamespaceService namespaceService;
private NodeRef document;
private NodeRef associatedDoc;
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";
private static String VALUE_ORIGINATOR = "fred@customer.com";
private static String VALUE_ADDRESSEE = "bill@example.com";
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 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_ORIGINATOR = "Originator";
private static String LABEL_ADDRESSEE = "Addressee";
private static String LABEL_ADDRESSEES = "Addressees";
private static String LABEL_SUBJECT = "Subject";
private static String LABEL_SENT_DATE = "Sent Date";
private static String LABEL_REFERENCES = "References";
/**
* Called during the transaction setup
@@ -44,12 +83,202 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest
// Get the required services
this.formService = (FormService)this.applicationContext.getBean("FormService");
this.namespaceService = (NamespaceService)this.applicationContext.getBean("NamespaceService");
// Authenticate as the system user
AuthenticationComponent authenticationComponent = (AuthenticationComponent) this.applicationContext
.getBean("authenticationComponent");
authenticationComponent.setSystemUserAsCurrentUser();
String guid = GUID.generate();
NodeRef rootNode = this.nodeService.getRootNode(
new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"));
Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>(1);
folderProps.put(ContentModel.PROP_NAME, "testFolder" + guid);
NodeRef folder = this.nodeService.createNode(
rootNode,
ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder" + guid),
ContentModel.TYPE_FOLDER,
folderProps).getChildRef();
// Create a node
Map<QName, Serializable> docProps = new HashMap<QName, Serializable>(1);
docProps.put(ContentModel.PROP_NAME, "testDocument" + guid + ".txt");
this.document = this.nodeService.createNode(
folder,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testDocument" + guid + ".txt"),
ContentModel.TYPE_CONTENT,
docProps).getChildRef();
// create a node to use as target of association
docProps.put(ContentModel.PROP_NAME, "associatedDocument" + guid + ".txt");
this.associatedDoc = this.nodeService.createNode(
folder,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "associatedDocument" + guid + ".txt"),
ContentModel.TYPE_CONTENT,
docProps).getChildRef();
// add standard titled aspect
Map<QName, Serializable> aspectProps = new HashMap<QName, Serializable>(2);
aspectProps.put(ContentModel.PROP_TITLE, VALUE_TITLE);
aspectProps.put(ContentModel.PROP_DESCRIPTION, VALUE_DESCRIPTION);
this.nodeService.addAspect(this.document, ContentModel.ASPECT_TITLED, aspectProps);
// add emailed aspect (has multiple value field)
aspectProps = new HashMap<QName, Serializable>(5);
aspectProps.put(ContentModel.PROP_ORIGINATOR, VALUE_ORIGINATOR);
aspectProps.put(ContentModel.PROP_ADDRESSEE, VALUE_ADDRESSEE);
List<String> addressees = new ArrayList<String>(2);
addressees.add(VALUE_ADDRESSEES1);
addressees.add(VALUE_ADDRESSEES2);
aspectProps.put(ContentModel.PROP_ADDRESSEES, (Serializable)addressees);
aspectProps.put(ContentModel.PROP_SUBJECT, VALUE_SUBJECT);
aspectProps.put(ContentModel.PROP_SENTDATE, VALUE_SENT_DATE);
this.nodeService.addAspect(this.document, ContentModel.ASPECT_MAILED, aspectProps);
// add referencing aspect (has association)
aspectProps.clear();
this.nodeService.addAspect(document, ContentModel.ASPECT_REFERENCING, aspectProps);
this.nodeService.createAssociation(this.document, this.associatedDoc, ContentModel.ASSOC_REFERENCES);
setComplete();
endTransaction();
}
public void testGetForm() throws Exception
{
Form form = this.formService.getForm("");
assertNull(form);
Form form = this.formService.getForm(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());
// 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<FieldDefinition> fieldDefs = form.getFieldDefinitions();
assertNotNull("Expecting to find fields", fieldDefs);
assertEquals("Expecting to find 19 fields", 19, 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<String, FieldDefinition> fieldDefMap = new HashMap<String, FieldDefinition>(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 descField = (PropertyFieldDefinition)fieldDefMap.get("cm:description");
PropertyFieldDefinition originatorField = (PropertyFieldDefinition)fieldDefMap.get("cm:originator");
PropertyFieldDefinition addresseeField = (PropertyFieldDefinition)fieldDefMap.get("cm:addressee");
PropertyFieldDefinition addresseesField = (PropertyFieldDefinition)fieldDefMap.get("cm:addressees");
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 cm:description field", descField);
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);
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 cm:description label to be " + LABEL_DESCRIPTION,
LABEL_DESCRIPTION, descField.getLabel());
assertEquals("Expecting cm:originator label to be " + LABEL_ORIGINATOR,
LABEL_ORIGINATOR, originatorField.getLabel());
assertEquals("Expecting cm:addressee label to be " + LABEL_ADDRESSEE,
LABEL_ADDRESSEE, addresseeField.getLabel());
assertEquals("Expecting cm:addressees label to be " + LABEL_ADDRESSEES,
LABEL_ADDRESSEES, addresseesField.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 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());
// TODO: get the constraint for the name field and check
/*
List<FieldConstraint> constraints = nameField.getConstraints();
assertEquals("Expecting 1 constraint for cm:name", constraints.size());
FieldConstraint constraint = constraints.get(0);
assertEquals("Expecting name of constraint to be 'REGEX'", "REGEX", constraint.getName());
Map<String, String> params = constraint.getParams();
assertNotNull("Expecting constraint parameters", params);
assertEquals("Expecting 2 constraint parameters", 2, params.size());
assertNotNull("Expecting an 'expression' constraint parameter", params.get("expression"));
assertNotNull("Expecting an 'requiresMatch' constraint parameter", params.get("requiresMatch"));
*/
// check details of the addressees field
assertEquals("Expecting cm:addressees type to be d:text", "d:text", addresseesField.getDataType());
assertFalse("Expecting cm:addressees to be mandatory", addresseesField.isMandatory());
assertTrue("Expecting cm:addressees to be multi valued", addresseesField.isRepeating());
assertNull("Expecting constraints for cm:addressees to be null", addresseesField.getConstraints());
// 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.getEnpointDirection().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<String, FormData.FieldData> fieldData = data.getData();
assertEquals(VALUE_TITLE, fieldData.get("cm:title").getValue());
assertEquals(VALUE_DESCRIPTION, fieldData.get("cm:description").getValue());
assertEquals(VALUE_ORIGINATOR, fieldData.get("cm:originator").getValue());
assertEquals(VALUE_ADDRESSEE, fieldData.get("cm:addressee").getValue());
assertEquals(VALUE_ADDRESSEES1, fieldData.get("cm:addressees_0").getValue());
assertEquals(VALUE_ADDRESSEES2, fieldData.get("cm:addressees_1").getValue());
assertEquals(VALUE_SUBJECT, fieldData.get("cm:subjectline").getValue());
Calendar calTestValue = Calendar.getInstance();
calTestValue.setTime(VALUE_SENT_DATE);
Calendar calServiceValue = Calendar.getInstance();
calServiceValue.setTime((Date)fieldData.get("cm:sentdate").getValue());
assertEquals(calTestValue.getTimeInMillis(), calServiceValue.getTimeInMillis());
List<String> targets = (List<String>)fieldData.get("cm:references").getValue();
assertEquals("Expecting 1 target", 1, targets.size());
assertEquals(this.associatedDoc.toString(), targets.get(0));
}
// == Test the JavaScript API ==

View File

@@ -32,46 +32,24 @@ import java.util.Map;
*
* @author Gavin Cornwell
*/
public class PropertyFieldDefiniton extends FieldDefinition
public class PropertyFieldDefinition extends FieldDefinition
{
protected String dataType;
protected boolean mandatory;
protected boolean repeats;
protected boolean mandatory = false;
protected boolean repeats = false;
protected List<FieldConstraint> constraints;
/**
* Constructs a FieldDefinition
* Default constructor
*
* @param name The name of the property
* @param label The display label of the property
* @param description The description of the property
* @param dataType The data type of the property
* @param defaultValue Default value of the property
* @param binding Binding of the property
* @param protectedField Whether the property should be read only
* @param mandatory Whether the property is mandatory
* @param repeats Whether the property can contain multiple values
* @param group The group the property belongs to
* @param constraints List of constraints the property has
*/
// TODO: Look at the Builder pattern to reduce the size of the constructor!!
public PropertyFieldDefiniton(String name, String label, String description,
String dataType, String defaultValue, String binding,
boolean protectedField, boolean mandatory, boolean repeats,
FieldGroup group, List<FieldConstraint> constraints)
public PropertyFieldDefinition(String name, String dataType)
{
this.name = name;
this.label = label;
this.description = description;
this.defaultValue = defaultValue;
this.binding = binding;
this.protectedField = protectedField;
this.group = group;
super(name);
this.dataType = dataType;
this.mandatory = mandatory;
this.repeats = repeats;
this.constraints = constraints;
}
/**
@@ -94,6 +72,16 @@ public class PropertyFieldDefiniton extends FieldDefinition
{
return this.mandatory;
}
/**
* Sets whether the property is mandatory
*
* @param mandatory true if it is mandatory
*/
public void setMandatory(boolean mandatory)
{
this.mandatory = mandatory;
}
/**
* Determines if the property can contain multiple values
@@ -105,6 +93,16 @@ public class PropertyFieldDefiniton extends FieldDefinition
return this.repeats;
}
/**
* Sets whether the property can contain multiple values
*
* @param repeats true if the field can contain multiple values
*/
public void setRepeating(boolean repeats)
{
this.repeats = repeats;
}
/**
* Returns a list of constraints the property may have
*
@@ -116,6 +114,38 @@ public class PropertyFieldDefiniton extends FieldDefinition
return this.constraints;
}
/**
* Sets the list of FieldConstraint objects for the property
*
* @param constraints List of FieldConstraint objects
*/
public void setConstraints(List<FieldConstraint> constraints)
{
this.constraints = constraints;
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("name=").append(this.name);
buffer.append(", dataType=").append(this.dataType);
buffer.append(", label=").append(this.label);
buffer.append(", description=").append(this.description);
buffer.append(", binding=").append(this.binding);
buffer.append(", defaultValue=").append(this.defaultValue);
buffer.append(", group=").append(this.group);
buffer.append(", protectedField=").append(this.protectedField);
buffer.append(", mandatory=").append(this.mandatory);
buffer.append(", repeats=").append(this.repeats);
buffer.append(", constraints=").append(this.constraints);
buffer.append(")");
return buffer.toString();
}
/**
* Represents a constraint on a property field
*/

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2005-2008 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.processor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Abstract base class for all FormProcessor implementations provides
* a regex pattern match to test for processor applicability
*
* @author Gavin Cornwell
*/
public abstract class AbstractFormProcessor implements FormProcessor
{
private static final Log logger = LogFactory.getLog(AbstractFormProcessor.class);
protected FormProcessorRegistry processorRegistry;
protected String matchPattern;
protected boolean active = true;
/**
* Sets the form process registry
*
* @param processorRegistry The FormProcessorRegistry instance
*/
public void setProcessorRegistry(FormProcessorRegistry processorRegistry)
{
this.processorRegistry = processorRegistry;
}
/**
* Sets the match pattern
*
* @param pattern The regex pattern to use to determine if this processor is applicable
*/
public void setMatchPattern(String pattern)
{
this.matchPattern = pattern;
}
/**
* Sets whether this processor is active
*
* @param active true if the processor should be active
*/
public void setActive(boolean active)
{
this.active = active;
}
/**
* Registers this processor with the processor registry
*/
public void register()
{
if (this.processorRegistry == null)
{
if (logger.isWarnEnabled())
logger.warn("Property 'processorRegistry' has not been set. Ignoring auto-registration of processor: " + this);
return;
}
if (this.matchPattern == null)
{
if (logger.isWarnEnabled())
logger.warn("Property 'matchPattern' has not been set. Ignoring auto-registration of processor: " + this);
return;
}
// register this instance
this.processorRegistry.addProcessor(this);
}
/*
* @see org.alfresco.repo.forms.processor.FormProcessor#isActive()
*/
public boolean isActive()
{
return this.active;
}
/*
* @see org.alfresco.repo.forms.processor.FormProcessor#isApplicable(java.lang.String)
*/
public boolean isApplicable(String item)
{
// TODO: do a regular expression match on the pattern supplied to
// determine if the processor matches
// NOTE: For now just return true as there is only going to be one
// form processor instance
return true;
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("active=").append(this.active);
buffer.append(", matchPattern=").append(this.matchPattern);
buffer.append(")");
return buffer.toString();
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2005-2008 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.processor;
import org.alfresco.repo.forms.Form;
import org.alfresco.repo.forms.FormData;
import org.alfresco.repo.forms.FormException;
/**
* Abstract base class for all FormProcessor implementations that wish to use the
* handler mechanism.
*
* @author Gavin Cornwell
*/
public abstract class AbstractFormProcessorByHandlers extends AbstractFormProcessor
{
protected HandlerRegistry handlerRegistry;
/**
* Sets the form processor handler registry
*
* @param handlerRegistry The FormProcessorHandlerRegistry instance
*/
public void setHandlerRegistry(HandlerRegistry handlerRegistry)
{
this.handlerRegistry = handlerRegistry;
}
/**
* 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)
* @param item The item to generate a form for
* @return The generated Form
*/
public Form generate(String item)
{
if (this.handlerRegistry == null)
{
throw new FormException("Property 'handlerRegistry' has not been set.");
}
// get the typed object representing the item
Object typedItem = getTypedItem(item);
// create an empty Form
Form form = new Form(item);
// execute each applicable handler
for (Handler handler: this.handlerRegistry.getApplicableHandlers(typedItem))
{
form = handler.handleGenerate(typedItem, form);
}
return form;
}
/**
* 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)
* @param item The item to save the form for
* @param data The object representing the form data
*/
public void persist(String item, FormData data)
{
if (this.handlerRegistry == null)
{
throw new FormException("Property 'handlerRegistry' has not been set.");
}
// get the typed object representing the item
Object typedItem = getTypedItem(item);
// execute each applicable handler
for (Handler handler: this.handlerRegistry.getApplicableHandlers(typedItem))
{
handler.handlePersist(typedItem, data);
}
}
/**
* Returns a typed Object representing the given item.
* <p>
* Subclasses that represent a form type will return a typed object
* that is then passed to each of it's handlers, the handlers can
* therefore safely cast the Object to the type they expect.
*
* @param item The item to get a typed object for
* @return The typed object
*/
protected abstract Object getTypedItem(String item);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2005-2008 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.processor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Abstract base class for all Handler implementations.
*
* @author Gavin Cornwell
*/
public abstract class AbstractHandler implements Handler
{
private static final Log logger = LogFactory.getLog(AbstractHandler.class);
protected HandlerRegistry handlerRegistry;
protected boolean active = true;
/**
* Sets the handler registry
*
* @param handlerRegistry The FormProcessorHandlerRegistry instance
*/
public void setHandlerRegistry(HandlerRegistry handlerRegistry)
{
this.handlerRegistry = handlerRegistry;
}
/**
* Sets whether this processor is active
*
* @param active true if the processor should be active
*/
public void setActive(boolean active)
{
this.active = active;
}
/**
* Registers this handler with the handler registry
*/
public void register()
{
if (handlerRegistry == null)
{
if (logger.isWarnEnabled())
logger.warn("Property 'handlerRegistry' has not been set. Ignoring auto-registration of handler: " + this);
return;
}
// register this instance
handlerRegistry.addHandler(this);
}
/*
* @see org.alfresco.repo.forms.processor.Handler#isActive()
*/
public boolean isActive()
{
return this.active;
}
/*
* @see org.alfresco.repo.forms.processor.FormProcessorHandler#isApplicable(java.lang.String)
*/
public boolean isApplicable(Object item)
{
// by default all handlers are applicable
return true;
}
/*
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuilder buffer = new StringBuilder(super.toString());
buffer.append(" (");
buffer.append("active=").append(this.isActive());
buffer.append(")");
return buffer.toString();
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2005-2008 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.processor;
import org.alfresco.repo.forms.Form;
import org.alfresco.repo.forms.FormData;
/**
* Interface definition of a form processor which is responsible
* for generating a Form representation of a data source, for example a
* repository node, a task or an XML schema and for persisting the
* form data back to the data source.
*
* @author Gavin Cornwell
*/
public interface FormProcessor
{
/**
* Determines whether this form processor is applicable for
* the supplied item
*
* @param item The item the form is being generated for
* @return true if the processor is applicable
*/
public boolean isApplicable(String item);
/**
* Determines whether this form processor is active
*
* @return true if the processor is active
*/
public boolean isActive();
/**
* Returns a Form representation for an item
*
* @param item The item to generate a Form object for
* @return The Form representation
*/
public Form generate(String item);
/**
* Persists the given object representing the form data
* for an item
*
* @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);
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2005-2008 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.processor;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Holds a FormProcessor implementation for each of the types of form that
* can be processed. By default a node, task and XML schema form processor
* are available.
* <p>
* Given an item the registry selects the relevant form processor, the match
* is performed via pattern matching on the supplied string.
*
* @author Gavin Cornwell
*/
public class FormProcessorRegistry
{
/** Logger */
private static Log logger = LogFactory.getLog(FormProcessorRegistry.class);
protected List<FormProcessor> processors;
/**
* Constructs the registry
*/
public FormProcessorRegistry()
{
this.processors = new ArrayList<FormProcessor>(4);
}
/**
* Registers a form processor
*
* @param processor The FormProcessor to regsiter
*/
public void addProcessor(FormProcessor processor)
{
if (processor.isActive())
{
this.processors.add(processor);
if (logger.isDebugEnabled())
logger.debug("Registered processor: " + processor);
}
else if (logger.isWarnEnabled())
{
logger.warn("Ignored registration of processor " + processor + "as it was marked as inactive");
}
}
/**
* Returns a FormProcessor for the provided item.
* <p>
* Each registered processors is asked if it is applicable for
* the given item, the first processor to positively respond
* that is also active is selected and returned.
*
* @param item The item to find a form processor for
* @return An applicable FormProcessor
*/
public FormProcessor getApplicableFormProcessor(String item)
{
FormProcessor selectedProcessor = null;
// iterate round the processors and fall out once the first
// active applicable processor is found
for (FormProcessor processor : this.processors)
{
if (processor.isActive() && processor.isApplicable(item))
{
selectedProcessor = processor;
break;
}
}
if (logger.isDebugEnabled())
logger.debug("Returning applicable processor: " + selectedProcessor);
return selectedProcessor;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2005-2008 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.processor;
import org.alfresco.repo.forms.Form;
import org.alfresco.repo.forms.FormData;
/**
* Interface definition for a handler which is used to process all or part
* of a form, if it is applicable to the item being processed and it's active
*
* @author Gavin Cornwell
*/
public interface Handler
{
// TODO: Investigate whether Generics can be used instead of Object
/**
* Determines whether the handler is applicable for the given item.
* <p>
* Handlers all relating to the same type of form can cast the Object
* to a more appropriate object, for example all the Node based handlers
* can expect a NodeRef object and therefore cast to that.
*
* @param item An object representing the item to handle
* @return true if the handler is applicable
*/
public boolean isApplicable(Object item);
/**
* Determines whether the handler is active
*
* @return true if the handler is active
*/
public boolean isActive();
/**
* Handles the generation of a Form.
* <p>
* Handlers all relating to the same type of form can cast the Object
* to a more appropriate object, for example all the Node based handlers
* can expect a NodeRef object and therefore cast to that.
*
* @param item The item to generate a Form for
* @param form The Form object
* @return The modified Form object
*/
public Form handleGenerate(Object item, Form form);
/**
* Handles the persistence of form data for the given item.
* <p>
* Handlers all relating to the same type of form can cast the item Object
* to a more appropriate object, for example all the Node based handlers
* can expect a NodeRef object and therefore cast to that.
*
* @param item The item to persist the form data for
* @param data The form data
*/
public void handlePersist(Object item, FormData data);
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2005-2008 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.processor;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Holds a list of handlers for a type of form processor, the handlers are called
* in sequence to check their applicability, if the handler applies to the item
* being processed it's generate or persist method is called.
*
* @see org.alfresco.repo.forms.processor.Handler
* @author Gavin Cornwell
*/
public class HandlerRegistry
{
private static final Log logger = LogFactory.getLog(HandlerRegistry.class);
protected List<Handler> handlers;
/**
* Constructs the registry
*/
public HandlerRegistry()
{
this.handlers = new ArrayList<Handler>(4);
}
/**
* Registers a handler
*
* @param handler The Handler to regsiter
*/
public void addHandler(Handler handler)
{
if (handler.isActive())
{
this.handlers.add(handler);
if (logger.isDebugEnabled())
logger.debug("Registered handler: " + handler + " in register: " + this);
}
else if (logger.isWarnEnabled())
{
logger.warn("Ignored registration of handler " + handler + "as it was marked as inactive");
}
}
/**
* Returns a list of handlers applicable for the given item
*
* @param item The item the form is being processed for
* @return List of applicable Handler objects
*/
public List<Handler> getApplicableHandlers(Object item)
{
List<Handler> applicableHandlers = new ArrayList<Handler>(4);
// iterate round the handlers and add each active applicable
// handler to the list
for (Handler handler : this.handlers)
{
if (handler.isActive() && handler.isApplicable(item))
{
applicableHandlers.add(handler);
}
}
if (logger.isDebugEnabled())
logger.debug("Returning applicable handlers: " + applicableHandlers);
return applicableHandlers;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2005-2008 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.processor;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
/**
* FormProcessor implementation that can generate and persist Form objects
* for repository nodes.
*
* @author Gavin Cornwell
*/
public class NodeFormProcessor extends AbstractFormProcessorByHandlers
{
/** Services */
protected NodeService nodeService;
/**
* Sets the node service
*
* @param nodeService The NodeService instance
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/*
* @see org.alfresco.repo.forms.processor.AbstractFormProcessor#getTypedItem(java.lang.String)
*/
protected Object getTypedItem(String item)
{
// create NodeRef representation
NodeRef nodeRef = new NodeRef(item);
// check the node itself exists
if (this.nodeService.exists(nodeRef) == false)
{
throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
}
else
{
// all Node based handlers can expect to get a NodeRef
return nodeRef;
}
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright (C) 2005-2008 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.processor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.forms.AssociationFieldDefinition;
import org.alfresco.repo.forms.FieldDefinition;
import org.alfresco.repo.forms.Form;
import org.alfresco.repo.forms.FormData;
import org.alfresco.repo.forms.FormException;
import org.alfresco.repo.forms.PropertyFieldDefinition;
import org.alfresco.repo.forms.AssociationFieldDefinition.Direction;
import org.alfresco.repo.forms.FormData.FieldData;
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.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Handler to handle the generation and persistence of a Form object for a repository node.
* <p>
* This handler will add all properties (including those of any aspects applied) and
* associations of the node to the Form.
*
* @author Gavin Cornwell
*/
public class NodeHandler extends AbstractHandler
{
private static final Log logger = LogFactory.getLog(NodeHandler.class);
/** Services */
protected NodeService nodeService;
protected DictionaryService dictionaryService;
protected NamespaceService namespaceService;
/**
* Sets the node service
*
* @param nodeService The NodeService instance
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Sets the data dictionary service
*
* @param dictionaryService The DictionaryService instance
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Sets the namespace service
*
* @param namespaceService The NamespaceService instance
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/*
* @see org.alfresco.repo.forms.processor.FormProcessorHandler#handleGenerate(java.lang.Object, org.alfresco.repo.forms.Form)
*/
public Form handleGenerate(Object item, Form form)
{
if (logger.isDebugEnabled())
logger.debug("Generating form for: " + item);
// 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);
if (logger.isDebugEnabled())
logger.debug("Returning form: " + form);
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)
{
// nothing yet
}
/**
* Sets up the field definitions for the form
*/
@SuppressWarnings("unchecked")
private void setupFields(NodeRef nodeRef, Form form)
{
FormData formData = new FormData();
// get data dictionary definition for node
QName type = this.nodeService.getType(nodeRef);
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
Map<QName, PropertyDefinition> propDefs = typeDef.getProperties();
Map<QName, Serializable> 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));
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<ConstraintDefinition> constraints = propDef.getConstraints();
if (constraints != null && constraints.size() > 0)
{
List<FieldConstraint> fieldConstraints =
new ArrayList<FieldConstraint>(constraints.size());
for (ConstraintDefinition constraintDef : constraints)
{
// TODO: We need to define a common interface for all constraints
// so that we can determine the name and parameters without
// having to know all the implementations
/*
Constraint constraint = constraintDef.getConstraint();
FieldConstraint fieldConstraint = fieldDef.new FieldConstraint(constraint.toString(),
new HashMap<String,String>());
fieldConstraints.add(fieldConstraint);
*/
}
fieldDef.setConstraints(fieldConstraints);
}
form.addFieldDefinition(fieldDef);
// get the field value and add to the form data object
Serializable fieldData = propValues.get(propDef.getName());
if (fieldData != null)
{
if (fieldData instanceof List)
{
List list = (List)fieldData;
String fieldName = fieldDef.getName();
for (int x = 0; x < list.size(); x++)
{
Object repeatingVal = list.get(x);
formData.addData(fieldName + "_" + x, repeatingVal);
}
}
else
{
formData.addData(fieldDef.getName(), fieldData);
}
}
}
// add target association data
List<AssociationRef> associations = this.nodeService.getTargetAssocs(nodeRef,
RegexQNamePattern.MATCH_ALL);
if (associations.size() > 0)
{
// create internal cache of association definitions created
Map<String, AssociationFieldDefinition> assocFieldDefs =
new HashMap<String, AssociationFieldDefinition>(associations.size());
for (AssociationRef assoc : associations)
{
// get the name of the association
QName assocType = assoc.getTypeQName();
String assocName = assocType.toPrefixString(this.namespaceService);
String assocValue = assoc.getTargetRef().toString();
// setup the field definition for the association if it hasn't before
AssociationFieldDefinition fieldDef = assocFieldDefs.get(assocName);
if (fieldDef == null)
{
AssociationDefinition assocDef = this.dictionaryService.getAssociation(assocType);
if (assocDef == null)
{
throw new FormException("Failed to find associaton definition for association: " + assocType);
}
fieldDef = new AssociationFieldDefinition(assocName,
assocDef.getTargetClass().getName().toPrefixString(
this.namespaceService), Direction.TARGET);
String title = assocDef.getTitle();
if (title == 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 Form and to internal cache
form.addFieldDefinition(fieldDef);
assocFieldDefs.put(assocName, fieldDef);
}
if (fieldDef.isEndpointMany())
{
// add the value as a List (or add to the list if the form data
// is already present)
List<String> targets = (List<String>)formData.getData().get(assocName);
if (targets == null)
{
targets = new ArrayList<String>(4);
formData.addData(assocName, targets);
}
// add the assoc value to the list
targets.add(assocValue);
}
else
{
// there should only be one value
formData.addData(assocName, assocValue);
}
}
}
// TODO: Add source association definitions and data
// set the form data
form.setFormData(formData);
}
}