Adding new files missed yesterday from merge of re-factored form.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21155 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2010-07-14 11:15:21 +00:00
parent 57107790f6
commit 240e0d9f05
7 changed files with 1324 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.forms.processor.workflow;
import org.alfresco.service.namespace.QName;
/**
* @author Nick Smith
*
*/
public class DataKeyInfo
{
private final String dataKey;
private final QName qName;
private final FieldType fieldType;
private final boolean isAdd;
private DataKeyInfo(String dataKey, QName qName, FieldType fieldType, boolean isAdd)
{
this.dataKey = dataKey;
this.qName = qName;
this.fieldType = fieldType;
this.isAdd = isAdd;
}
public static DataKeyInfo makeAssociationDataKeyInfo(String dataKey, QName qName, boolean isAdd)
{
return new DataKeyInfo(dataKey, qName, FieldType.ASSOCIATION, isAdd);
}
public static DataKeyInfo makePropertyDataKeyInfo(String dataKey, QName qName)
{
return new DataKeyInfo(dataKey, qName, FieldType.PROPERTY, true);
}
public static DataKeyInfo makeTransientDataKeyInfo(String dataKey)
{
return new DataKeyInfo(dataKey, null, FieldType.TRANSIENT, true);
}
/**
* @return the dataKey
*/
public String getDataKey()
{
return dataKey;
}
/**
* @return the qName
*/
public QName getqName()
{
return qName;
}
/**
* @return the fieldType
*/
public FieldType getFieldType()
{
return fieldType;
}
/**
* @return the isAdd
*/
public boolean isAdd()
{
return isAdd;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.forms.processor.workflow;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import static org.alfresco.repo.forms.processor.node.FormFieldConstants.*;
/**
* @author Nick Smith
*
*/
public class DataKeyMatcher
{
/**
* A regular expression which can be used to match property names. These
* names will look like <code>"prop_cm_name"</code>. The pattern can also be
* used to extract the "cm" and the "name" parts.
*/
private final static Pattern propertyNamePattern = Pattern.compile("(^[a-zA-Z0-9]+)_([a-zA-Z0-9_]+$)");
public DataKeyMatcher(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* A regular expression which can be used to match association names. These
* names will look like <code>"assoc_cm_references_added"</code>. The
* pattern can also be used to extract the "cm", the "name" and the suffix
* parts.
*/
private final static Pattern associationNamePattern = Pattern.compile("(^[a-zA-Z0-9]+)_([a-zA-Z0-9_]+)(_[a-zA-Z]+$)");
private final NamespaceService namespaceService;
/**
*
* @param dataKey
* @return
*/
public DataKeyInfo match(String dataKey)
{
if (dataKey.startsWith(PROP_DATA_PREFIX))
{
return matchProperty(dataKey);
}
else if(dataKey.startsWith(ASSOC_DATA_PREFIX))
{
return matchAssociation(dataKey);
}
// No match found.
return null;
}
private DataKeyInfo matchAssociation(String dataKey)
{
String keyName = dataKey.substring(ASSOC_DATA_PREFIX.length());
Matcher matcher = associationNamePattern.matcher(keyName);
if (!matcher.matches())
{
return null;
}
QName qName = getQName(matcher);
String suffix = matcher.group(3);
boolean isAdd = !(ASSOC_DATA_REMOVED_SUFFIX.equals(suffix));
return DataKeyInfo.makeAssociationDataKeyInfo(keyName, qName, isAdd);
}
private DataKeyInfo matchProperty(String dataKey)
{
String keyName = dataKey.substring(PROP_DATA_PREFIX.length());
Matcher matcher = propertyNamePattern.matcher(keyName);
if (matcher.matches())
{
QName qName = getQName(matcher);
return DataKeyInfo.makePropertyDataKeyInfo(keyName, qName);
}
return DataKeyInfo.makeTransientDataKeyInfo(keyName);
}
private QName getQName(Matcher matcher)
{
String prefix = matcher.group(1);
String localName = matcher.group(2);
QName qName = QName.createQName(prefix, localName, namespaceService);
return qName;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.forms.processor.workflow;
/**
* @author Nick Smith
*
*/
public enum FieldType
{
ASSOCIATION,
PROPERTY,
TRANSIENT;
}

View File

@@ -0,0 +1,235 @@
/*
* 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.processor.workflow;
import java.io.Serializable;
import java.util.Map;
import org.alfresco.repo.forms.FormData;
import org.alfresco.repo.forms.Item;
import org.alfresco.repo.forms.FormData.FieldData;
import org.alfresco.repo.forms.processor.FieldProcessorRegistry;
import org.alfresco.repo.forms.processor.node.ContentModelFormProcessor;
import org.alfresco.repo.forms.processor.node.ItemData;
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.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author Nick Smith
*/
public class TaskFormProcessor extends ContentModelFormProcessor<WorkflowTask, WorkflowTask>
{
/** Logger */
private static final Log LOGGER = LogFactory.getLog(TaskFormProcessor.class);
private static final TypedPropertyValueGetter valueGetter = new TypedPropertyValueGetter();
private DataKeyMatcher keyMatcher;
private WorkflowService workflowService;
// Constructor for Spring
public TaskFormProcessor()
{
super();
}
// Constructor for tests.
public TaskFormProcessor(WorkflowService workflowService, NamespaceService namespaceService,
DictionaryService dictionaryService, FieldProcessorRegistry fieldProcessorRegistry)
{
this.workflowService = workflowService;
this.namespaceService = namespaceService;
this.dictionaryService = dictionaryService;
this.fieldProcessorRegistry = fieldProcessorRegistry;
this.keyMatcher = new DataKeyMatcher(namespaceService);
}
@Override
protected WorkflowTask getTypedItem(Item item)
{
ParameterCheck.mandatory("item", item);
String id = item.getId();
WorkflowTask task = workflowService.getTaskById(id);
return task;
}
@Override
protected WorkflowTask internalPersist(WorkflowTask task, FormData data)
{
TaskUpdater updater = new TaskUpdater(task.id, workflowService);
ItemData<WorkflowTask> itemData = makeItemData(task);
for (FieldData fieldData : data)
{
addFieldToSerialize(updater, itemData, fieldData);
}
return updater.update();
}
private void addFieldToSerialize(TaskUpdater updater,
ItemData<?> itemData,
FieldData fieldData)
{
String name = fieldData.getName();
DataKeyInfo keyInfo = keyMatcher.match(name);
if ((keyInfo == null || FieldType.TRANSIENT == keyInfo.getFieldType()) &&
LOGGER.isWarnEnabled())
{
LOGGER.warn("Ignoring unrecognized field: " + name);
}
if (keyInfo != null)
{
QName fullName = keyInfo.getqName();
Object rawValue = fieldData.getValue();
if (FieldType.PROPERTY == keyInfo.getFieldType())
{
Serializable propValue = getPropertyValueToPersist(fullName, rawValue, itemData);
// TODO What if the user wants to set prop to null?
if (propValue != null)
{
updater.addProperty(fullName, propValue);
}
}
else if (FieldType.ASSOCIATION == keyInfo.getFieldType())
{
if (rawValue instanceof String)
{
updater.changeAssociation(fullName, (String) rawValue, keyInfo.isAdd());
}
}
}
}
private Serializable getPropertyValueToPersist(QName fullName,
Object value,
ItemData<?> itemData)
{
PropertyDefinition propDef = itemData.getPropertyDefinition(fullName);
if (propDef == null)
{
propDef = dictionaryService.getProperty(fullName);
}
if (propDef != null)
{
return valueGetter.getValue(value, propDef);
}
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemType(java
* .lang.Object)
*/
@Override
protected String getItemType(WorkflowTask item)
{
TypeDefinition typeDef = item.definition.metadata;
return typeDef.getName().toPrefixString(namespaceService);
}
/*
* (non-Javadoc)
*
* @see
* org.alfresco.repo.forms.processor.FilteredFormProcessor#getItemURI(java
* .lang.Object)
*/
@Override
protected String getItemURI(WorkflowTask item)
{
// TODO Check this URL is OK.
return "/api/task-instances/" + item.id;
}
/*
* @see
* org.alfresco.repo.forms.processor.task.ContentModelFormProcessor#getLogger
* ()
*/
@Override
protected Log getLogger()
{
return LOGGER;
}
@Override
protected TypeDefinition getBaseType(WorkflowTask task)
{
return task.definition.metadata;
}
@Override
protected Map<QName, Serializable> getPropertyValues(WorkflowTask task)
{
return task.properties;
}
@Override
protected Map<QName, Serializable> getAssociationValues(WorkflowTask item)
{
return item.properties;
}
@Override
protected Map<String, Object> getTransientValues(WorkflowTask item)
{
return null;
}
/**
* Sets the Workflow Service.
*
* @param workflowService
*/
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
/*
* (non-Javadoc)
*
* @seeorg.alfresco.repo.forms.processor.task.ContentModelFormProcessor#
* setNamespaceService(org.alfresco.service.namespace.NamespaceService)
*/
@Override
public void setNamespaceService(NamespaceService namespaceService)
{
super.setNamespaceService(namespaceService);
this.keyMatcher = new DataKeyMatcher(namespaceService);
}
}

View File

@@ -0,0 +1,507 @@
/*
* 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.processor.workflow;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.alfresco.repo.forms.FieldDefinition;
import org.alfresco.repo.forms.Form;
import org.alfresco.repo.forms.FormData;
import org.alfresco.repo.forms.Item;
import org.alfresco.repo.forms.FormData.FieldData;
import org.alfresco.repo.forms.processor.node.DefaultFieldProcessor;
import org.alfresco.repo.forms.processor.node.FormFieldConstants;
import org.alfresco.repo.forms.processor.node.MockClassAttributeDefinition;
import org.alfresco.repo.forms.processor.node.MockFieldProcessorRegistry;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowNode;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.NamespaceServiceMemoryImpl;
import org.alfresco.service.namespace.QName;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* @author Nick Smith
*/
public class TaskFormProcessorTest extends TestCase
{
private static final String TASK_DEF_NAME = "TaskDef";
private static final String TASK_ID = "Real Id";
private static final QName DESC_NAME = WorkflowModel.PROP_DESCRIPTION;
private static final QName STATUS_NAME = WorkflowModel.PROP_STATUS;
private static final QName PROP_WITH_ = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "some_prop");
private static final QName ACTORS_NAME = WorkflowModel.ASSOC_POOLED_ACTORS;
private static final QName ASSIGNEE_NAME = WorkflowModel.ASSOC_ASSIGNEE;
private static final QName ASSOC_WITH_ = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "some_assoc");
private static final NodeRef FAKE_NODE = new NodeRef(NamespaceService.BPM_MODEL_1_0_URI + "/FakeNode");
private WorkflowService workflowService;
private TaskFormProcessor processor;
private WorkflowTask task;
private NamespaceService namespaceService;
private WorkflowTask newTask;
public void testGetTypedItem() throws Exception
{
try
{
processor.getTypedItem(null);
fail("Should have thrown an Exception here!");
}
catch (IllegalArgumentException e)
{
// Do nothing!
}
try
{
processor.getTypedItem(new Item("task", "bad id"));
fail("Should have thrown an Exception here!");
}
catch (WorkflowException e)
{
// Do nothing!
}
Item item = new Item("task", TASK_ID);
WorkflowTask result = processor.getTypedItem(item);
assertNotNull(result);
assertEquals(TASK_ID, result.id);
}
public void testGenerateSetsItemAndUrl() throws Exception
{
Item item = new Item("task", TASK_ID);
Form form = processor.generate(item, null, null, null);
Item formItem = form.getItem();
assertEquals(item.getId(), formItem.getId());
assertEquals(item.getKind(), formItem.getKind());
String expType = NamespaceService.BPM_MODEL_PREFIX + ":" + TASK_DEF_NAME;
assertEquals(expType, formItem.getType());
assertEquals("/api/task-instances/" + TASK_ID, formItem.getUrl());
}
public void testGenerateSingleProperty()
{
// Check Status field is added to Form.
String fieldName = STATUS_NAME.toPrefixString(namespaceService);
List<String> fields = Arrays.asList(fieldName);
Form form = processForm(fields);
checkSingleProperty(form, fieldName, WorkflowTaskState.IN_PROGRESS);
// Check Status field is added to Form, when explicitly typed as a
// property.
String fullPropertyName = "prop:" + fieldName;
fields = Arrays.asList(fullPropertyName);
form = processForm(fields);
checkSingleProperty(form, fieldName, WorkflowTaskState.IN_PROGRESS);
}
public void testGenerateSingleAssociation()
{
// Check Assignee field is added to Form.
String fieldName = ASSIGNEE_NAME.toPrefixString(namespaceService);
List<String> fields = Arrays.asList(fieldName);
Form form = processForm(fields);
Serializable fieldData = (Serializable) Arrays.asList(FAKE_NODE.toString());
// checkSingleAssociation(form, fieldName, fieldData);
// Check Assignee field is added to Form, when explicitly typed as an
// association.
String fullAssociationName = "assoc:" + fieldName;
fields = Arrays.asList(fullAssociationName);
form = processForm(fields);
// checkSingleAssociation(form, fieldName, fieldData);
}
public void testIgnoresUnknownFields() throws Exception
{
String fakeFieldName = NamespaceService.BPM_MODEL_PREFIX + ":" + "Fake Field";
String statusFieldName = STATUS_NAME.toPrefixString(namespaceService);
List<String> fields = Arrays.asList(fakeFieldName, statusFieldName);
Form form = processForm(fields);
checkSingleProperty(form, statusFieldName, WorkflowTaskState.IN_PROGRESS);
}
public void testGenerateDefaultForm() throws Exception
{
Form form = processForm(null);
List<String> fieldDefs = form.getFieldDefinitionNames();
assertEquals(6, fieldDefs.size());
assertTrue(fieldDefs.contains(ASSIGNEE_NAME.toPrefixString(namespaceService)));
assertTrue(fieldDefs.contains(ACTORS_NAME.toPrefixString(namespaceService)));
assertTrue(fieldDefs.contains(DESC_NAME.toPrefixString(namespaceService)));
assertTrue(fieldDefs.contains(STATUS_NAME.toPrefixString(namespaceService)));
Serializable fieldData = (Serializable) Arrays.asList(FAKE_NODE.toString());
FormData formData = form.getFormData();
assertEquals(4, formData.getNumberOfFields());
// assertEquals(fieldData, formData.getFieldData("assoc_bpm_assignee").getValue());
assertEquals(WorkflowTaskState.IN_PROGRESS, formData.getFieldData("prop_bpm_status").getValue());
}
public void testPersistPropertyChanged() throws Exception
{
String fieldName = DESC_NAME.toPrefixString(namespaceService);
String dataKey = makeDataKeyName(fieldName, FormFieldConstants.PROP_DATA_PREFIX);
String value = "New Description";
processPersist(dataKey, value);
Map<QName, Serializable> actualProperties = retrievePropertyies();
assertEquals(1, actualProperties.size());
assertEquals(value, actualProperties.get(DESC_NAME));
}
public void testPersistPropertyWith_() throws Exception
{
String fieldName = PROP_WITH_.toPrefixString(namespaceService);
String dataKey = makeDataKeyName(fieldName, FormFieldConstants.PROP_DATA_PREFIX);
String value = "New _ Value";
processPersist(dataKey, value);
Map<QName, Serializable> actualProperties = retrievePropertyies();
assertEquals(1, actualProperties.size());
assertEquals(value, actualProperties.get(PROP_WITH_));
}
@SuppressWarnings("unchecked")
private Map<QName, Serializable> retrievePropertyies()
{
ArgumentCaptor<Map> mapArg = ArgumentCaptor.forClass(Map.class);
verify(workflowService).updateTask(eq(TASK_ID), mapArg.capture(), anyMap(), anyMap());
return mapArg.getValue();
}
public void testPersistAssociationAdded() throws Exception
{
String fieldName = ACTORS_NAME.toPrefixString(namespaceService);
String dataKey = makeDataKeyName(fieldName, FormFieldConstants.ASSOC_DATA_PREFIX);
dataKey = dataKey + FormFieldConstants.ASSOC_DATA_ADDED_SUFFIX;
String nodeRef1 = FAKE_NODE.toString() + "1";
String nodeRef2 = FAKE_NODE.toString() + "2";
String value = nodeRef1 + ", " + nodeRef2;
processPersist(dataKey, value);
Map<QName, List<NodeRef>> actualAddedAssocs = retrieveAssociations(true);
assertEquals(1, actualAddedAssocs.size());
List<NodeRef> nodeRefs = actualAddedAssocs.get(ACTORS_NAME);
assertNotNull(nodeRefs);
assertEquals(2, nodeRefs.size());
assertTrue(nodeRefs.contains(new NodeRef(nodeRef1)));
assertTrue(nodeRefs.contains(new NodeRef(nodeRef2)));
}
public void testPersistAssociationsRemoved() throws Exception
{
String fieldName = ASSIGNEE_NAME.toPrefixString(namespaceService);
String dataKey = makeDataKeyName(fieldName, FormFieldConstants.ASSOC_DATA_PREFIX);
dataKey = dataKey + FormFieldConstants.ASSOC_DATA_REMOVED_SUFFIX;
String value = FAKE_NODE.toString();
processPersist(dataKey, value);
Map<QName, List<NodeRef>> actualRemovedAssocs = retrieveAssociations(false);
assertEquals(1, actualRemovedAssocs.size());
List<NodeRef> nodeRefs = actualRemovedAssocs.get(ASSIGNEE_NAME);
assertNotNull(nodeRefs);
assertEquals(1, nodeRefs.size());
assertTrue(nodeRefs.contains(FAKE_NODE));
}
public void testPersistAssociationAddedWith_() throws Exception
{
String fieldName = ASSOC_WITH_.toPrefixString(namespaceService);
String dataKey = makeDataKeyName(fieldName, FormFieldConstants.ASSOC_DATA_PREFIX);
dataKey = dataKey + FormFieldConstants.ASSOC_DATA_ADDED_SUFFIX;
String nodeRef1 = FAKE_NODE.toString() + "1";
String nodeRef2 = FAKE_NODE.toString() + "2";
String value = nodeRef1 + ", " + nodeRef2;
processPersist(dataKey, value);
Map<QName, List<NodeRef>> actualAddedAssocs = retrieveAssociations(true);
assertEquals(1, actualAddedAssocs.size());
List<NodeRef> nodeRefs = actualAddedAssocs.get(ASSOC_WITH_);
assertNotNull(nodeRefs);
assertEquals(2, nodeRefs.size());
assertTrue(nodeRefs.contains(new NodeRef(nodeRef1)));
assertTrue(nodeRefs.contains(new NodeRef(nodeRef2)));
}
@SuppressWarnings("unchecked")
private Map<QName, List<NodeRef>> retrieveAssociations(boolean added)
{
ArgumentCaptor<Map> mapArg = ArgumentCaptor.forClass(Map.class);
if (added)
{
verify(workflowService).updateTask(eq(TASK_ID), anyMap(), mapArg.capture(), anyMap());
}
else
{
verify(workflowService).updateTask(eq(TASK_ID), anyMap(), anyMap(), mapArg.capture());
}
return mapArg.getValue();
}
private void processPersist(String dataKey, String value)
{
FormData data = new FormData();
data.addFieldData(dataKey, value);
Item item = new Item("task", TASK_ID);
WorkflowTask persistedItem = (WorkflowTask) processor.persist(item, data);
assertEquals(newTask, persistedItem);
}
private Form processForm(List<String> fields)
{
Item item = new Item("task", TASK_ID);
Form form = processor.generate(item, fields, null, null);
return form;
}
private void checkSingleProperty(Form form, String fieldName, Serializable fieldData)
{
checkSingleField(form, fieldName, fieldData, "prop_");
}
private void checkSingleAssociation(Form form, String fieldName, Serializable fieldData)
{
checkSingleField(form, fieldName, fieldData, "assoc_");
}
private void checkSingleField(Form form, String fieldName, Serializable fieldData, String prefix)
{
List<FieldDefinition> fieldDefs = form.getFieldDefinitions();
assertEquals(1, fieldDefs.size());
FieldDefinition fieldDef = fieldDefs.get(0);
assertEquals(fieldName, fieldDef.getName());
String dataKey = fieldDef.getDataKeyName();
String expDataKey = makeDataKeyName(fieldName, prefix);
assertEquals(expDataKey, dataKey);
FieldData data = form.getFormData().getFieldData(dataKey);
assertEquals(fieldData, data.getValue());
}
/**
* @param fieldName
* @param prefix
* @return
*/
private String makeDataKeyName(String fieldName, String prefix)
{
return prefix + fieldName.replace(":", "_");
}
/*
* @see junit.framework.TestCase#setUp()
*/
@Override
protected void setUp() throws Exception
{
super.setUp();
task = makeTask();
workflowService = makeWorkflowService();
DictionaryService dictionaryService = makeDictionaryService();
namespaceService = makeNamespaceService();
MockFieldProcessorRegistry fieldProcessorRegistry = new MockFieldProcessorRegistry(namespaceService,
dictionaryService);
DefaultFieldProcessor defaultProcessor = makeDefaultFieldProcessor(dictionaryService);
processor = makeTaskFormProcessor(dictionaryService, fieldProcessorRegistry, defaultProcessor);
}
private TaskFormProcessor makeTaskFormProcessor(DictionaryService dictionaryService,
MockFieldProcessorRegistry fieldProcessorRegistry, DefaultFieldProcessor defaultProcessor)
{
TaskFormProcessor processor1 = new TaskFormProcessor();
processor1.setWorkflowService(workflowService);
processor1.setNamespaceService(namespaceService);
processor1.setDictionaryService(dictionaryService);
processor1.setFieldProcessorRegistry(fieldProcessorRegistry);
return processor1;
}
private DefaultFieldProcessor makeDefaultFieldProcessor(DictionaryService dictionaryService) throws Exception
{
DefaultFieldProcessor defaultProcessor = new DefaultFieldProcessor();
defaultProcessor.setDictionaryService(dictionaryService);
defaultProcessor.setNamespaceService(namespaceService);
defaultProcessor.afterPropertiesSet();
return defaultProcessor;
}
private WorkflowTask makeTask()
{
WorkflowTask result = new WorkflowTask();
result.id = TASK_ID;
result.state = WorkflowTaskState.IN_PROGRESS;
result.definition = makeTaskDefinition();
result.properties = makeTaskProperties();
return result;
}
private HashMap<QName, Serializable> makeTaskProperties()
{
HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();
properties.put(STATUS_NAME, WorkflowTaskState.IN_PROGRESS);
properties.put(ASSIGNEE_NAME, FAKE_NODE);
return properties;
}
private WorkflowTaskDefinition makeTaskDefinition()
{
WorkflowTaskDefinition definition = new WorkflowTaskDefinition();
definition.id = "DefinitionId";
definition.metadata = makeTypeDef();
definition.node = mock(WorkflowNode.class);
return definition;
}
private TypeDefinition makeTypeDef()
{
TypeDefinition typeDef = mock(TypeDefinition.class);
QName name = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, TASK_DEF_NAME);
when(typeDef.getName()).thenReturn(name);
// Set up task property definitions
Map<QName, PropertyDefinition> propertyDefs = makeTaskPropertyDefs();
when(typeDef.getProperties()).thenReturn(propertyDefs);
// Set up task association definitions.
Map<QName, AssociationDefinition> associationDefs = makeTaskAssociationDefs();
when(typeDef.getAssociations()).thenReturn(associationDefs);
return typeDef;
}
private Map<QName, PropertyDefinition> makeTaskPropertyDefs()
{
Map<QName, PropertyDefinition> properties = new HashMap<QName, PropertyDefinition>();
QName textType = DataTypeDefinition.TEXT;
// Add a Description property
PropertyDefinition descValue = MockClassAttributeDefinition.mockPropertyDefinition(DESC_NAME, textType);
properties.put(DESC_NAME, descValue);
// Add a Status property
PropertyDefinition titleValue = MockClassAttributeDefinition.mockPropertyDefinition(STATUS_NAME, textType);
properties.put(STATUS_NAME, titleValue);
// Add a Status property
PropertyDefinition with_ = MockClassAttributeDefinition.mockPropertyDefinition(PROP_WITH_, textType);
properties.put(PROP_WITH_, with_);
return properties;
}
private Map<QName, AssociationDefinition> makeTaskAssociationDefs()
{
Map<QName, AssociationDefinition> associations = new HashMap<QName, AssociationDefinition>();
QName actorName = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "Actor");
// Add Assigneee association
MockClassAttributeDefinition assigneeDef = MockClassAttributeDefinition.mockAssociationDefinition(
ASSIGNEE_NAME, actorName);
associations.put(ASSIGNEE_NAME, assigneeDef);
// Add Assigneee association
MockClassAttributeDefinition actorsDef = MockClassAttributeDefinition.mockAssociationDefinition(ACTORS_NAME,
actorName);
associations.put(ACTORS_NAME, actorsDef);
// Add association with _
MockClassAttributeDefinition with_ = MockClassAttributeDefinition.mockAssociationDefinition(ASSOC_WITH_,
actorName);
associations.put(ASSOC_WITH_, with_);
return associations;
}
private NamespaceService makeNamespaceService()
{
NamespaceServiceMemoryImpl nsService = new NamespaceServiceMemoryImpl();
nsService.registerNamespace(NamespaceService.BPM_MODEL_PREFIX, NamespaceService.BPM_MODEL_1_0_URI);
return nsService;
}
@SuppressWarnings("unchecked")
private DictionaryService makeDictionaryService()
{
DictionaryService mock = mock(DictionaryService.class);
when(mock.getAnonymousType((QName) any(), (Collection<QName>) any())).thenReturn(task.definition.metadata);
return mock;
}
@SuppressWarnings("unchecked")
private WorkflowService makeWorkflowService()
{
WorkflowService service = mock(WorkflowService.class);
when(service.getTaskById(anyString())).thenAnswer(new Answer<WorkflowTask>()
{
public WorkflowTask answer(InvocationOnMock invocation) throws Throwable
{
String id = (String) invocation.getArguments()[0];
if (TASK_ID.equals(id))
return task;
else
throw new WorkflowException("Task Id not found!");
}
});
this.newTask = new WorkflowTask();
newTask.id = TASK_ID;
when(service.updateTask(anyString(), anyMap(), anyMap(), anyMap())).thenReturn(newTask);
return service;
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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.processor.workflow;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A utility class for updating workflow tasks. This is a stateful object that
* accumulates a set of updates to a task and then commits all the updates when
* the update() method is called.
*
* @author Nick Smith
*/
public class TaskUpdater
{
/** Logger */
private static final Log LOGGER = LogFactory.getLog(TaskUpdater.class);
private final String taskId;
private final WorkflowService workflowService;
private Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
private Map<QName, List<NodeRef>> add = new HashMap<QName, List<NodeRef>>();
private Map<QName, List<NodeRef>> remove = new HashMap<QName, List<NodeRef>>();
public TaskUpdater(String taskId, WorkflowService workflowService)
{
this.taskId = taskId;
this.workflowService = workflowService;
}
public WorkflowTask update()
{
WorkflowTask result = workflowService.updateTask(taskId, properties, add, remove);
properties = new HashMap<QName, Serializable>();
add = new HashMap<QName, List<NodeRef>>();
remove = new HashMap<QName, List<NodeRef>>();
return result;
}
public void addProperty(QName name, Serializable value)
{
properties.put(name, value);
}
public void addAssociation(QName name, List<NodeRef> value)
{
add.put(name, value);
}
public void removeAssociation(QName name, List<NodeRef> value)
{
remove.put(name, value);
}
public boolean changeAssociation(QName name, String nodeRefs, boolean isAdd)
{
List<NodeRef> value = getNodeRefs(nodeRefs);
if (value == null)
{
return false;
}
Map<QName, List<NodeRef>> map = getAssociationMap(isAdd);
if (map != null)
{
map.put(name, value);
return true;
}
return false;
}
/**
* @param suffix
* @return
*/
private Map<QName, List<NodeRef>> getAssociationMap(boolean isAdd)
{
Map<QName, List<NodeRef>> map = null;
if (isAdd)
{
map = add;
}
else
{
map = remove;
}
return map;
}
private List<NodeRef> getNodeRefs(Object value)
{
String[] nodeRefIds = ((String) value).split(",");
List<NodeRef> nodeRefs = new ArrayList<NodeRef>(nodeRefIds.length);
for (String nodeRefString : nodeRefIds)
{
String nodeRefId = nodeRefString.trim();
if (NodeRef.isNodeRef(nodeRefId))
{
NodeRef nodeRef = new NodeRef(nodeRefId);
nodeRefs.add(nodeRef);
}
else
{
logNodeRefError(nodeRefId);
}
}
return nodeRefs;
}
private void logNodeRefError(String nodeRefId)
{
if (LOGGER.isWarnEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Target Node: ").append(nodeRefId);
msg.append(" is not a valid NodeRef and has been ignored.");
LOGGER.warn(msg.toString());
}
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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.processor.workflow;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.alfresco.repo.forms.FormException;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.json.JSONArray;
import org.json.JSONException;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* @author Nick Smith
*/
public class TypedPropertyValueGetter
{
public static final String ON = "on";
public Serializable getValue(Object value, PropertyDefinition propDef)
{
if (value == null)
{
return null;
}
Serializable typedValue = null;
// before persisting check data type of property
if (propDef.isMultiValued())
{
typedValue = processMultiValuedType(value);
}
else if (isBooleanProperty(propDef))
{
typedValue = processBooleanValue(value);
}
else if (isLocaleProperty(propDef))
{
typedValue = processLocaleValue(value);
}
else if (value instanceof String)
{
String valStr = (String) value;
// make sure empty strings stay as empty strings, everything else
// should be represented as null
if(valStr.isEmpty() && !isTextProperty(propDef))
{
// Do nothing, leave typedValue as null.
}
else
{
typedValue = valStr;
}
}
else if (value instanceof Serializable)
{
typedValue = (Serializable) value;
}
else
{
throw new FormException("Property values must be of a Serializable type! Value type: " + value.getClass());
}
return typedValue;
}
private boolean isTextProperty(PropertyDefinition propDef)
{
return propDef.getDataType().getName().equals(DataTypeDefinition.TEXT) ||
propDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT);
}
private Boolean processBooleanValue(Object value)
{
// check for browser representation of true, that being "on"
if (value instanceof String && ON.equals(value))
{
return Boolean.TRUE;
}
else
{
return Boolean.FALSE; // TODO Check this line is OK with Gav.
}
}
private boolean isBooleanProperty(PropertyDefinition propDef)
{
return propDef.getDataType().getName().equals(DataTypeDefinition.BOOLEAN);
}
private Serializable processLocaleValue(Object value)
{
if (value instanceof String)
{
return I18NUtil.parseLocale((String) value);
}
else
{
throw new FormException("Locale property values must be represented as a String! Value is of type: "
+ value.getClass());
}
}
private boolean isLocaleProperty(PropertyDefinition propDef)
{
return propDef.getDataType().getName().equals(DataTypeDefinition.LOCALE);
}
private Serializable processMultiValuedType(Object value)
{
// depending on client the value could be a comma separated string,
// a List object or a JSONArray object
if (value instanceof String)
{
String stringValue = (String) value;
return processMultiValueString(stringValue);
}
else if (value instanceof JSONArray)
{
// if value is a JSONArray convert to List of Object
JSONArray jsonArr = (JSONArray) value;
return processJSONArray(jsonArr);
}
else if (value instanceof List<?>)
{
// persist the list
return (Serializable) value;
}
else
{
throw new FormException("The value is an unsupported multi-value type: " + value);
}
}
private Serializable processJSONArray(JSONArray jsonArr)
{
int arrLength = jsonArr.length();
ArrayList<Object> list = new ArrayList<Object>(arrLength);
try
{
for (int x = 0; x < arrLength; x++)
{
list.add(jsonArr.get(x));
}
}
catch (JSONException je)
{
throw new FormException("Failed to convert JSONArray to List", je);
}
return list;
}
private Serializable processMultiValueString(String stringValue)
{
if (stringValue.length() == 0)
{
// empty string for multi-valued properties
// should be stored as null
return null;
}
else
{
// if value is a String convert to List of String persist the List
String[] values = stringValue.split(",");
List<String> valueList = Arrays.asList(values);
return new ArrayList<String>(valueList);
}
}
}