/**
* Copyright (c) 2005-2008 Intalio inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Intalio inc. - initial API and implementation
*
* $Id: TaskManagementServicesFacade.java 5440 2006-06-09 08:58:15Z imemruk $
* $Log:$
*/
package org.intalio.tempo.workflow.task.xml;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import javax.xml.namespace.QName;
import org.apache.axiom.om.OMElement;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException;
import org.intalio.tempo.workflow.auth.ACL;
import org.intalio.tempo.workflow.auth.AuthIdentifierSet;
import org.intalio.tempo.workflow.task.Task;
import org.intalio.tempo.workflow.task.TaskState;
import org.intalio.tempo.workflow.task.attachments.Attachment;
import org.intalio.tempo.workflow.task.attachments.AttachmentMetadata;
import org.intalio.tempo.workflow.task.traits.IChainableTask;
import org.intalio.tempo.workflow.task.traits.ICompleteReportingTask;
import org.intalio.tempo.workflow.task.traits.IInstanceBoundTask;
import org.intalio.tempo.workflow.task.traits.IProcessBoundTask;
import org.intalio.tempo.workflow.task.traits.ITaskWithAttachments;
import org.intalio.tempo.workflow.task.traits.ITaskWithDeadline;
import org.intalio.tempo.workflow.task.traits.ITaskWithInput;
import org.intalio.tempo.workflow.task.traits.ITaskWithOutput;
import org.intalio.tempo.workflow.task.traits.ITaskWithPriority;
import org.intalio.tempo.workflow.task.traits.ITaskWithState;
import org.intalio.tempo.workflow.task.traits.InitTask;
import org.intalio.tempo.workflow.util.RequiredArgumentException;
import org.intalio.tempo.workflow.util.xml.InvalidInputFormatException;
import org.intalio.tempo.workflow.util.xml.XmlBeanUnmarshaller;
import org.intalio.tempo.workflow.util.xml.XsdDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import com.intalio.bpms.workflow.taskManagementServices20051109.Attachments;
import com.intalio.bpms.workflow.taskManagementServices20051109.TaskMetadata;
/**
* As noted by Alexis, we are now careful of using dispose properly throughout this class.
* You can refer to this for more info:
* <a href="http://xmlbeans.apache.org/docs/2.0.0/reference/org/apache/xmlbeans/XmlCursor.html#dispose%28%29">Dispose cursors</a>
*
*/
public class TaskUnmarshaller extends XmlBeanUnmarshaller {
private static final Logger _logger = LoggerFactory.getLogger(TaskUnmarshaller.class);
private static final String[] actions = new String[] { "claim", "revoke", "save", "complete", "dismiss" };
public TaskUnmarshaller() {
super(TaskXMLConstants.TASK_NAMESPACE, TaskXMLConstants.TASK_NAMESPACE_PREFIX);
}
// for compatibility usage
public Task unmarshalTaskFromMetadata(OMElement rootElement) throws InvalidInputFormatException {
return unmarshalTaskFromMetadata(unmarshalTaskMetadata(rootElement));
}
private TaskMetadata unmarshalTaskMetadata(OMElement rootElement) throws InvalidInputFormatException {
try {
XmlObject xmlObject = XmlObject.Factory.parse(rootElement.getXMLStreamReader());
if (_logger.isDebugEnabled()) {
_logger.debug(xmlObject.xmlText());
}
XmlCursor xmlCursor = xmlObject.newCursor();
xmlCursor.toStartDoc();
xmlCursor.toNextToken();
TaskMetadata taskMetadata = com.intalio.bpms.workflow.taskManagementServices20051109.Task.Factory.newInstance().addNewMetadata();
taskMetadata.set(xmlCursor.getObject());
xmlCursor.dispose();
return taskMetadata;
} catch (InvalidInputFormatException e) {
throw e;
} catch (XmlException e) {
throw new InvalidInputFormatException(e);
}
}
private Task unmarshalTaskFromMetadata(TaskMetadata taskMetadata) throws XmlValueOutOfRangeException {
if (taskMetadata == null) {
throw new RequiredArgumentException("rootElement");
}
checkNS(taskMetadata);
String taskID = taskMetadata.getTaskId();
if (taskID == null) {
throw new InvalidInputFormatException("No task id specified");
}
String taskStateStr = taskMetadata.getTaskState();
String taskTypeStr = taskMetadata.getTaskType();
String description = taskMetadata.getDescription();
String processID = taskMetadata.getProcessId();
String instanceID = taskMetadata.getInstanceId();
AuthIdentifierSet userOwners = new AuthIdentifierSet(Arrays.asList(taskMetadata.getUserOwnerArray()));
AuthIdentifierSet roleOwners = new AuthIdentifierSet(Arrays.asList(taskMetadata.getRoleOwnerArray()));
Integer priority = null;
if (taskMetadata.xgetPriority() != null && taskMetadata.xgetPriority().validate()) {
priority = taskMetadata.getPriority();
}
String formURLStr = taskMetadata.getFormUrl();
URI formURL = null;
if (formURLStr != null) {
try {
formURL = new URI(formURLStr);
} catch (URISyntaxException e) {
throw new InvalidInputFormatException(e);
}
} else {
throw new InvalidInputFormatException("No URL found for form");
}
String failureCode = taskMetadata.getFailureCode();
String failureReason = taskMetadata.getFailureReason();
expectElementValue(taskMetadata, "userProcessEndpoint");
// TODO: these violate the WSDL! do something
expectElementValue(taskMetadata, "userProcessNamespaceURI");
String completeSOAPAction = taskMetadata.getUserProcessCompleteSOAPAction();
Attachments attachmentsElement = taskMetadata.getAttachments();
String isChainedBeforeStr = expectElementValue(taskMetadata, "isChainedBefore");
String previousTaskID = expectElementValue(taskMetadata, "previousTaskId");
Class<? extends Task> taskClass = TaskTypeMapper.getTypeClassByName(taskTypeStr);
Task resultTask = null;
TaskState taskState = null;
if (!ITaskWithState.class.isAssignableFrom(taskClass)) {
forbidParameter(taskStateStr, "task state");
forbidParameter(failureCode, "failure code");
forbidParameter(failureReason, "failure reason");
} else {
try {
taskState = (taskStateStr == null) ? TaskState.READY : TaskState.valueOf(taskStateStr.toUpperCase());
} catch (IllegalArgumentException e) {
_logger.error("Error in unmarshalling task from metadata", e);
throw new InvalidInputFormatException("Unknown task state: '" + taskStateStr + "'");
}
}
if (IProcessBoundTask.class.isAssignableFrom(taskClass)) {
if (taskMetadata.xgetProcessId() == null) {
/*
* The following line will be commented to support versions of
* designer that do not generate the processId
*/
// throw new
// InvalidInputFormatException("ProcessID not specified");
}
} else {
forbidParameter(processID, "processID");
}
if (IInstanceBoundTask.class.isAssignableFrom(taskClass)) {
if (taskMetadata.xgetInstanceId() == null) {
/*
* The following line will be commented to support versions of
* designer that do not generate the InstanceId
*/
// throw new
// InvalidInputFormatException("instanceID not specified");
}
} else {
forbidParameter(instanceID, "instanceID");
}
if (ICompleteReportingTask.class.isAssignableFrom(taskClass)) {
requireParameter(completeSOAPAction, "completion SOAPAction");
} else {
forbidParameter(completeSOAPAction, "completion SOAPAction");
}
if (!ITaskWithAttachments.class.isAssignableFrom(taskClass)) {
forbidParameter(attachmentsElement, "task attachment(s)");
}
if (!IChainableTask.class.isAssignableFrom(taskClass)) {
forbidParameter(isChainedBeforeStr, "is-chained-before flag");
forbidParameter(previousTaskID, "previous chained task ID");
}
resultTask = TaskTypeMapper.getNewInstance(taskClass, taskID, formURL);
resultTask.getUserOwners().addAll(userOwners);
resultTask.getRoleOwners().addAll(roleOwners);
resultTask.setDescription(description == null ? "" : description);
try {
Calendar creationDate = taskMetadata.getCreationDate();
if (creationDate != null)
resultTask.setCreationDate(creationDate.getTime());
else
resultTask.setCreationDate(new Date());
} catch (XmlValueOutOfRangeException e) {
resultTask.setCreationDate(new Date());
}
for (String action : actions) {
ACL acl = readACL(taskMetadata, action);
authorize(resultTask, action, acl);
}
if (ITaskWithState.class.isAssignableFrom(taskClass)) {
ITaskWithState taskWithState = (ITaskWithState) resultTask;
taskWithState.setState(taskState);
if (taskWithState.getState().equals(TaskState.FAILED)) {
requireParameter(failureCode, "failure code");
taskWithState.setFailureCode(failureCode);
taskWithState.setFailureReason(failureReason == null ? "" : failureReason);
} else {
forbidParameter(failureCode, "failure code");
forbidParameter(failureReason, "failure reason");
}
}
if (InitTask.class.isAssignableFrom(taskClass)) {
InitTask task = (InitTask) resultTask;
String uri1 = taskMetadata.getInitMessageNamespaceURI();
if (uri1 != null)
task.setInitMessageNamespaceURI(URI.create(uri1));
String soap = taskMetadata.getInitOperationSOAPAction();
if (soap != null)
task.setInitOperationSOAPAction(soap);
String uri2 = taskMetadata.getProcessEndpoint();
if (uri2 != null)
task.setProcessEndpoint(URI.create(uri2));
}
if (IProcessBoundTask.class.isAssignableFrom(taskClass)) {
if (taskMetadata.xgetProcessId() != null && processID != null)
((IProcessBoundTask) resultTask).setProcessID(processID);
}
if (IInstanceBoundTask.class.isAssignableFrom(taskClass)) {
if (taskMetadata.xgetInstanceId() != null && taskMetadata.getInstanceId() != null) {
((IInstanceBoundTask) resultTask).setInstanceId(instanceID);
}
}
if (ICompleteReportingTask.class.isAssignableFrom(taskClass)) {
((ICompleteReportingTask) resultTask).setCompleteSOAPAction(completeSOAPAction);
}
if (ITaskWithAttachments.class.isAssignableFrom(taskClass)) {
ITaskWithAttachments taskWithAttachments = (ITaskWithAttachments) resultTask;
if (attachmentsElement != null) {
for (int i = 0; i < attachmentsElement.sizeOfAttachmentArray(); i++) {
com.intalio.bpms.workflow.taskManagementServices20051109.Attachment attachmentElement = attachmentsElement.getAttachmentArray(i);
if (attachmentElement != null) {
// The following line has been added to handle the case
// where an attachment element is present
// but do not contain any data: no title,
// nodescription , ect...
// The reason why is this is added is to
// handle the initial initialization on
// Designer
// In which designer generates by default an
// attachment element as a part of the
// initialization of the message
// even if no attachment is used
// TODO: When Designer and Server will
// support "lazy initialization", this line
// can be omitted
XmlCursor attCursor = attachmentElement.newCursor();
try {
if (attCursor.getTextValue().trim().length() != 0) {
com.intalio.bpms.workflow.taskManagementServices20051109.AttachmentMetadata attachmentMetadata = attachmentElement
.getAttachmentMetadata();
AttachmentMetadata metadata = new AttachmentMetadata();
String mimeType = attachmentMetadata.getMimeType();
if (mimeType != null) {
metadata.setMimeType(mimeType);
}
String fileName = attachmentMetadata.getFileName();
if (fileName != null)
metadata.setFileName(fileName);
String title = attachmentMetadata.getTitle();
if (title != null)
metadata.setTitle(title);
String description2 = attachmentMetadata.getDescription();
if (description2 != null)
metadata.setDescription(description2);
try {
Calendar cal = attachmentMetadata.getCreationDate();
if ((cal != null)) {
metadata.setCreationDate(new XsdDateTime(cal.toString()).getTime());
}
} catch (Exception e) {
_logger.warn("Error in unmarshalling creation date in attachment from metadata");
metadata.setCreationDate(new Date());
}
String payloadURLStr = attachmentElement.getPayloadUrl();
URL payloadURL;
try {
payloadURL = new URL(payloadURLStr);
} catch (MalformedURLException e) {
throw new InvalidInputFormatException(e);
}
Attachment attachment = new Attachment(metadata, payloadURL);
taskWithAttachments.addAttachment(attachment);
}
} finally {
attCursor.dispose();
}
}
}
}
}
if (IChainableTask.class.isAssignableFrom(taskClass)) {
IChainableTask chainableTask = (IChainableTask) resultTask;
if (isChainedBeforeStr != null) {
if ("1".equals(isChainedBeforeStr) || "true".equals(isChainedBeforeStr)) {
if (previousTaskID == null) {
throw new InvalidInputFormatException("tms:previousTaskId is required " + "if tms:isChainedBefore is true");
}
chainableTask.setPreviousTaskID(previousTaskID);
chainableTask.setChainedBefore(true);
} else {
if ((previousTaskID != null) && (!"".equals(previousTaskID))) {
throw new InvalidInputFormatException("tms:previousTaskId must be empty or not present " + "if tms:isChainedBefore is false");
}
}
} else {
if (previousTaskID != null) {
throw new InvalidInputFormatException("tms:isChainedBefore is required " + "if tms:previousTaskId is present");
}
}
}
// / the following is added to support task deadlines
if (ITaskWithDeadline.class.isAssignableFrom(taskClass)) {
ITaskWithDeadline taskWithDeadline = (ITaskWithDeadline) resultTask;
try {
Calendar deadline = taskMetadata.getDeadline();
if (deadline != null) {
taskWithDeadline.setDeadline(deadline.getTime());
} else {
// do nothing, deadline is null by default
}
} catch (XmlValueOutOfRangeException e) {
// do nothing, not a valid xml date
}
}
// the following is added to support task priorities
if (ITaskWithPriority.class.isAssignableFrom(taskClass)) {
ITaskWithPriority taskWithDeadline = (ITaskWithPriority) resultTask;
taskWithDeadline.setPriority(priority);
}
return resultTask;
}
private void authorize(Task resultTask, String action, ACL acl) {
for (String user : acl.getUserOwners()) {
resultTask.authorizeActionForUser(action, user);
}
for (String role : acl.getRoleOwners()) {
resultTask.authorizeActionForRole(action, role);
}
}
private ACL readACL(XmlObject root, String action) {
ACL acl = new ACL();
XmlObject el = expectElement(root, action + "Action");
if (el != null) {
acl.getUserOwners().addAll(expectAuthIdentifiers(el, "user"));
acl.getRoleOwners().addAll(expectAuthIdentifiers(el, "role"));
}
return acl;
}
private void checkTaskPayload(XmlObject containerElement) throws InvalidInputFormatException {
if (containerElement == null) {
throw new RequiredArgumentException("containerElement");
}
XmlCursor payloadCursor = containerElement.newCursor();
try {
if (!payloadCursor.toFirstChild()) {
throw new InvalidInputFormatException("Payload container element must contain exactly one child element");
}
if (payloadCursor.toNextSibling()) {
throw new InvalidInputFormatException("Task payload must consist of exactly one element.");
}
} finally {
payloadCursor.dispose();
}
}
public XmlObject unmarshalTaskInput(XmlObject inputContainerElement) throws InvalidInputFormatException {
checkTaskPayload(inputContainerElement);
return inputContainerElement;
}
// for compatibility usage
private Document unmarshalTaskPayload(OMElement containerElement) throws InvalidInputFormatException {
if (containerElement == null) {
throw new RequiredArgumentException("containerElement");
}
Iterator<OMElement> it = containerElement.getChildElements();
if (!it.hasNext()) {
throw new InvalidInputFormatException("Payload container element must contain exactly one child element");
}
Document result = null;
OMElement firstPayloadElement = it.next();
if (it.hasNext()) {
throw new InvalidInputFormatException("Task payload must consist of exactly one element.");
} else {
result = new XmlTooling().convertOMToDOM(firstPayloadElement);
}
return result;
}
// for compatibility usage
public Document unmarshalTaskOutput(OMElement outputContainerElement) throws InvalidInputFormatException {
return unmarshalTaskPayload(outputContainerElement);
}
public XmlObject unmarshalTaskOutput(XmlObject outputContainerElement) throws InvalidInputFormatException {
checkTaskPayload(outputContainerElement);
return outputContainerElement;
}
public Task unmarshalFullTask(OMElement rootElement) throws InvalidInputFormatException {
try {
XmlObject xmlObject = XmlObject.Factory.parse(rootElement.getXMLStreamReader());
return unmarshalFullTask(xmlObject);
} catch (XmlException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public TaskMetadata unmarshalPartialTask(OMElement rootElement) throws InvalidInputFormatException {
try {
XmlObject xmlObject = XmlObject.Factory.parse(rootElement.getXMLStreamReader());
requireElement(xmlObject, "metadata");
com.intalio.bpms.workflow.taskManagementServices20051109.Task taskElement = com.intalio.bpms.workflow.taskManagementServices20051109.Task.Factory
.newInstance();
TaskMetadata metadataElement = taskElement.addNewMetadata();
metadataElement.set(expectElement(xmlObject, "metadata"));
return metadataElement;
} catch (XmlException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public TaskMetadata unmarshalPartialTask2(OMElement rootElement) throws InvalidInputFormatException {
try {
XmlObject xmlObject = XmlObject.Factory.parse(rootElement.getXMLStreamReader());
com.intalio.bpms.workflow.taskManagementServices20051109.Task taskElement = com.intalio.bpms.workflow.taskManagementServices20051109.Task.Factory.newInstance();
TaskMetadata metadata = taskElement.addNewMetadata();
metadata.set(new TaskUnmarshaller().expectElement(xmlObject, "taskMetadata"));
return metadata;
} catch (XmlException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private Task unmarshalFullTask(XmlObject rootElement) throws InvalidInputFormatException {
if (rootElement == null) {
throw new RequiredArgumentException("rootElement");
}
Task resultTask = null;
requireElement(rootElement, "metadata");
com.intalio.bpms.workflow.taskManagementServices20051109.Task taskElement = com.intalio.bpms.workflow.taskManagementServices20051109.Task.Factory
.newInstance();
TaskMetadata metadataElement = taskElement.addNewMetadata();
metadataElement.set(expectElement(rootElement, "metadata"));
com.intalio.bpms.workflow.taskManagementServices20051109.TaskData inputElement = null;
XmlObject xmlInput = expectElement(rootElement, "input");
if (xmlInput != null) {
inputElement = taskElement.addNewInput();
inputElement.set(xmlInput);
}
com.intalio.bpms.workflow.taskManagementServices20051109.TaskData outputElement = null;
XmlObject xmlOutput = expectElement(rootElement, "output");
if (xmlOutput != null) {
outputElement = taskElement.addNewOutput();
outputElement.set(xmlOutput);
}
resultTask = unmarshalTaskFromMetadata(metadataElement);
if (resultTask instanceof ITaskWithInput) {
requireParameter(inputElement, "task input");
XmlObject input = unmarshalTaskInput(inputElement);
((ITaskWithInput) resultTask).setInput(serializeXMLObject(input));
} else {
forbidParameter(inputElement, "task input");
}
if ((resultTask instanceof ITaskWithOutput) && outputElement != null) {
requireParameter(outputElement, "task output");
XmlObject output = unmarshalTaskOutput(outputElement);
((ITaskWithOutput) resultTask).setOutput(serializeXMLObject(output));
} else {
forbidParameter(outputElement, "task output");
}
return resultTask;
}
/**
* Xmlbeans is wrapping the content with some <code>xml-fragment</code> tag
* Position the cursor on the data we really want.
*/
private String serializeXMLObject(XmlObject xmlObject) {
XmlCursor cursor = xmlObject.newCursor();
try {
cursor.toFirstChild();
XmlOptions opts = new XmlOptions();
opts.setSaveNoXmlDecl();
opts.setLoadReplaceDocumentElement(cursor.getName());
return cursor.xmlText(opts);
} finally {
cursor.dispose();
}
}
private void checkNS(XmlObject containerElement) throws InvalidInputFormatException {
if (containerElement == null) {
throw new RequiredArgumentException("containerElement");
}
XmlCursor payloadCursor = containerElement.newCursor();
try {
if (!payloadCursor.toFirstChild()) {
throw new InvalidInputFormatException("No taskmetadata element");
}
QName qName = payloadCursor.getName();
if (qName == null || qName.getNamespaceURI() == null || qName.getNamespaceURI().trim().length() == 0) {
throw new InvalidInputFormatException("No namespace defined");
}
} finally {
payloadCursor.dispose();
}
}
}