View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.dto;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.core.api.reflect.DataDefinition;
22  import org.kuali.rice.core.api.reflect.ObjectDefinition;
23  import org.kuali.rice.core.api.reflect.PropertyDefinition;
24  import org.kuali.rice.core.api.util.xml.XmlHelper;
25  import org.kuali.rice.core.api.util.xml.XmlJotter;
26  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
27  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
28  import org.kuali.rice.kew.api.KewApiServiceLocator;
29  import org.kuali.rice.kew.api.WorkflowRuntimeException;
30  import org.kuali.rice.kew.api.action.ActionRequest;
31  import org.kuali.rice.kew.api.action.ActionTaken;
32  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
33  import org.kuali.rice.kew.api.document.DocumentDetail;
34  import org.kuali.rice.kew.api.document.InvalidDocumentContentException;
35  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
36  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
37  import org.kuali.rice.kew.api.extension.ExtensionUtils;
38  import org.kuali.rice.kew.definition.AttributeDefinition;
39  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
40  import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
41  import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
42  import org.kuali.rice.kew.framework.postprocessor.AfterProcessEvent;
43  import org.kuali.rice.kew.framework.postprocessor.BeforeProcessEvent;
44  import org.kuali.rice.kew.framework.postprocessor.DeleteEvent;
45  import org.kuali.rice.kew.framework.postprocessor.DocumentLockingEvent;
46  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
47  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
48  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
49  import org.kuali.rice.kew.routeheader.StandardDocumentContent;
50  import org.kuali.rice.kew.rule.WorkflowRuleAttribute;
51  import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
52  import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
53  import org.kuali.rice.kew.rule.XmlConfiguredAttribute;
54  import org.kuali.rice.kew.rule.bo.RuleAttribute;
55  import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
56  import org.kuali.rice.kew.service.KEWServiceLocator;
57  import org.kuali.rice.kew.api.KewApiConstants;
58  import org.w3c.dom.Document;
59  import org.w3c.dom.Element;
60  import org.w3c.dom.NodeList;
61  import org.xml.sax.SAXException;
62  
63  import javax.xml.parsers.DocumentBuilder;
64  import javax.xml.parsers.DocumentBuilderFactory;
65  import javax.xml.parsers.ParserConfigurationException;
66  import javax.xml.transform.TransformerException;
67  import java.io.IOException;
68  import java.util.ArrayList;
69  import java.util.HashMap;
70  import java.util.Iterator;
71  import java.util.List;
72  import java.util.Map;
73  
74  /**
75   * Translates Workflow server side beans into client side VO beans.
76   *
77   * @author Kuali Rice Team (rice.collab@kuali.org)
78   */
79  public class DTOConverter {
80      private static final Logger LOG = Logger.getLogger(DTOConverter.class);
81  
82      public static String buildUpdatedDocumentContent(String existingDocContent,
83              DocumentContentUpdate documentContentUpdate, String documentTypeName) {
84          if (existingDocContent == null) {
85              existingDocContent = KewApiConstants.DEFAULT_DOCUMENT_CONTENT;
86          }
87          String documentContent = KewApiConstants.DEFAULT_DOCUMENT_CONTENT;
88          StandardDocumentContent standardDocContent = new StandardDocumentContent(existingDocContent);
89          try {
90              DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
91              Document document = builder.newDocument();
92              Element root = document.createElement(KewApiConstants.DOCUMENT_CONTENT_ELEMENT);
93              document.appendChild(root);
94              Element applicationContentElement = standardDocContent.getApplicationContent();
95              if (documentContentUpdate.getApplicationContent() != null) {
96                  // application content has changed
97                  if (!StringUtils.isEmpty(documentContentUpdate.getApplicationContent())) {
98                      applicationContentElement = document.createElement(KewApiConstants.APPLICATION_CONTENT_ELEMENT);
99                      XmlHelper.appendXml(applicationContentElement, documentContentUpdate.getApplicationContent());
100                 } else {
101                     // they've cleared the application content
102                     applicationContentElement = null;
103                 }
104             }
105             Element attributeContentElement = createDocumentContentSection(document,
106                     standardDocContent.getAttributeContent(), documentContentUpdate.getAttributeDefinitions(),
107                     documentContentUpdate.getAttributeContent(), KewApiConstants.ATTRIBUTE_CONTENT_ELEMENT,
108                     documentTypeName);
109             Element searchableContentElement = createDocumentContentSection(document,
110                     standardDocContent.getSearchableContent(), documentContentUpdate.getSearchableDefinitions(),
111                     documentContentUpdate.getSearchableContent(), KewApiConstants.SEARCHABLE_CONTENT_ELEMENT,
112                     documentTypeName);
113             if (applicationContentElement != null) {
114                 root.appendChild(applicationContentElement);
115             }
116             if (attributeContentElement != null) {
117                 root.appendChild(attributeContentElement);
118             }
119             if (searchableContentElement != null) {
120                 root.appendChild(searchableContentElement);
121             }
122             documentContent = XmlJotter.jotNode(document);
123         } catch (ParserConfigurationException e) {
124             throw new RiceRuntimeException("Failed to initialize XML parser.", e);
125         } catch (SAXException e) {
126             throw new InvalidDocumentContentException("Failed to parse XML.", e);
127         } catch (IOException e) {
128             throw new InvalidDocumentContentException("Failed to parse XML.", e);
129         } catch (TransformerException e) {
130             throw new InvalidDocumentContentException("Failed to parse XML.", e);
131         }
132         return documentContent;
133     }
134 
135     private static Element createDocumentContentSection(Document document, Element existingAttributeElement,
136             List<WorkflowAttributeDefinition> definitions, String content, String elementName,
137             String documentTypeName) throws TransformerException, SAXException, IOException, ParserConfigurationException {
138         Element contentSectionElement = existingAttributeElement;
139         // if they've updated the content, we're going to re-build the content section element from scratch
140         if (content != null) {
141             if (!org.apache.commons.lang.StringUtils.isEmpty(content)) {
142                 contentSectionElement = document.createElement(elementName);
143                 // if they didn't merely clear the content, let's build the content section element by combining the children
144                 // of the incoming XML content
145                 Element incomingAttributeElement = XmlHelper.readXml(content).getDocumentElement();
146                 NodeList children = incomingAttributeElement.getChildNodes();
147                 for (int index = 0; index < children.getLength(); index++) {
148                     contentSectionElement.appendChild(document.importNode(children.item(index), true));
149                 }
150             } else {
151                 contentSectionElement = null;
152             }
153         }
154         // if they have new definitions we're going to append those to the existing content section
155         if (definitions != null && !definitions.isEmpty()) {
156             String errorMessage = "";
157             boolean inError = false;
158             if (contentSectionElement == null) {
159                 contentSectionElement = document.createElement(elementName);
160             }
161             for (WorkflowAttributeDefinition definitionVO : definitions) {
162                 AttributeDefinition definition = convertWorkflowAttributeDefinition(definitionVO);
163                 ExtensionDefinition extensionDefinition = definition.getExtensionDefinition();
164                 Object attribute = ExtensionUtils.loadExtension(extensionDefinition);
165 
166                 if (attribute instanceof XmlConfiguredAttribute) {
167                     ((XmlConfiguredAttribute)attribute).setExtensionDefinition(RuleAttribute.to(definition.getRuleAttribute()));
168                 }
169                 boolean propertiesAsMap = false;
170                 if (KewApiConstants.RULE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType())) {
171                     propertiesAsMap = true;
172                 }
173                 if (propertiesAsMap) {
174                     for (org.kuali.rice.kew.api.document.PropertyDefinition propertyDefinitionVO : definitionVO
175                             .getPropertyDefinitions()) {
176                         if (attribute instanceof GenericXMLRuleAttribute) {
177                             ((GenericXMLRuleAttribute) attribute).getParamMap().put(propertyDefinitionVO.getName(),
178                                     propertyDefinitionVO.getValue());
179                         }
180                     }
181                 }
182 
183                 // validate inputs from client application if the attribute is capable
184                 if (attribute instanceof WorkflowAttributeXmlValidator) {
185                     List<WorkflowAttributeValidationError> errors =
186                             ((WorkflowAttributeXmlValidator) attribute).validateClientRoutingData();
187                     if (!errors.isEmpty()) {
188                         inError = true;
189                         errorMessage += "Error validating attribute " + definitionVO.getAttributeName() + " ";
190                         for (WorkflowAttributeValidationError error : errors) {
191                             errorMessage += error.getMessage() + " ";
192                         }
193                     }
194                 }
195                 // dont add to xml if attribute is in error
196                 if (!inError) {
197                     if (attribute instanceof WorkflowRuleAttribute) {
198                         String attributeDocContent = ((WorkflowRuleAttribute) attribute).getDocContent();
199                         if (!StringUtils.isEmpty(attributeDocContent)) {
200                             XmlHelper.appendXml(contentSectionElement, attributeDocContent);
201                         }
202                     } else if (attribute instanceof SearchableAttribute) {
203                         SearchableAttribute searchableAttribute = (SearchableAttribute) attribute;
204                         String searchableAttributeContent = searchableAttribute.generateSearchContent(extensionDefinition, documentTypeName,
205                                 definitionVO);
206                         if (!StringUtils.isBlank(searchableAttributeContent)) {
207                             XmlHelper.appendXml(contentSectionElement, searchableAttributeContent);
208                         }
209                     }
210                 }
211             }
212             if (inError) {
213                 throw new WorkflowRuntimeException(errorMessage);
214             }
215 
216         }
217         if (contentSectionElement != null) {
218             // always be sure and import the element into the new document, if it originated from the existing doc content
219             // and
220             // appended to it, it will need to be imported
221             contentSectionElement = (Element) document.importNode(contentSectionElement, true);
222         }
223         return contentSectionElement;
224     }
225 
226     /**
227      * New for Rice 2.0
228      */
229     public static AttributeDefinition convertWorkflowAttributeDefinition(WorkflowAttributeDefinition definition) {
230         if (definition == null) {
231             return null;
232         }
233 
234         ExtensionDefinition extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionByName(definition.getAttributeName());
235         if (extensionDefinition == null) {
236             throw new WorkflowRuntimeException("Extension " + definition.getAttributeName() + " not found");
237         }
238         RuleAttribute ruleAttribute = KEWServiceLocator.getRuleAttributeService().findByName(definition.getAttributeName());
239         if (ruleAttribute == null) {
240             throw new WorkflowRuntimeException("Attribute " + definition.getAttributeName() + " not found");
241         }
242 
243         ObjectDefinition objectDefinition = new ObjectDefinition(extensionDefinition.getResourceDescriptor());
244         if (definition.getParameters() != null) {
245             for (String parameter : definition.getParameters()) {
246                 objectDefinition.addConstructorParameter(new DataDefinition(parameter, String.class));
247             }
248         }
249         boolean propertiesAsMap = KewApiConstants.RULE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType()) || KewApiConstants
250                 .SEARCHABLE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType());
251         if (!propertiesAsMap && definition.getPropertyDefinitions() != null) {
252             for (org.kuali.rice.kew.api.document.PropertyDefinition propertyDefinition : definition
253                     .getPropertyDefinitions()) {
254                 objectDefinition.addProperty(new PropertyDefinition(propertyDefinition.getName(), new DataDefinition(
255                         propertyDefinition.getValue(), String.class)));
256             }
257         }
258 
259         return new AttributeDefinition(ruleAttribute, extensionDefinition, objectDefinition);
260     }
261 
262     /**
263      * Interface for a simple service providing RouteNodeInstanceS based on their IDs
264      */
265     public static interface RouteNodeInstanceLoader {
266         RouteNodeInstance load(String routeNodeInstanceID);
267     }
268 
269 
270     public static DocumentDetail convertDocumentDetailNew(DocumentRouteHeaderValue routeHeader) {
271         if (routeHeader == null) {
272             return null;
273         }
274         org.kuali.rice.kew.api.document.Document document = DocumentRouteHeaderValue.to(routeHeader);
275         DocumentDetail.Builder detail = DocumentDetail.Builder.create(document);
276         Map<String, RouteNodeInstance> nodeInstances = new HashMap<String, RouteNodeInstance>();
277         List<ActionRequest> actionRequestVOs = new ArrayList<ActionRequest>();
278         List<ActionRequestValue> rootActionRequests = KEWServiceLocator.getActionRequestService().getRootRequests(
279                 routeHeader.getActionRequests());
280         for (Iterator<ActionRequestValue> iterator = rootActionRequests.iterator(); iterator.hasNext(); ) {
281             ActionRequestValue actionRequest = iterator.next();
282             actionRequestVOs.add(ActionRequestValue.to(actionRequest));
283             RouteNodeInstance nodeInstance = actionRequest.getNodeInstance();
284             if (nodeInstance == null) {
285                 continue;
286             }
287             if (nodeInstance.getRouteNodeInstanceId() == null) {
288                 throw new IllegalStateException(
289                         "Error creating document detail structure because of NULL node instance id.");
290             }
291             nodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
292         }
293         detail.setActionRequests(actionRequestVOs);
294         List<org.kuali.rice.kew.api.document.node.RouteNodeInstance> nodeInstanceVOs =
295                 new ArrayList<org.kuali.rice.kew.api.document.node.RouteNodeInstance>();
296         for (Iterator<RouteNodeInstance> iterator = nodeInstances.values().iterator(); iterator.hasNext(); ) {
297             RouteNodeInstance nodeInstance = iterator.next();
298             nodeInstanceVOs.add(RouteNodeInstance.to(nodeInstance));
299         }
300         detail.setRouteNodeInstances(nodeInstanceVOs);
301         List<ActionTaken> actionTakenVOs = new ArrayList<ActionTaken>();
302         for (Object element : routeHeader.getActionsTaken()) {
303             ActionTakenValue actionTaken = (ActionTakenValue) element;
304             actionTakenVOs.add(ActionTakenValue.to(actionTaken));
305         }
306         detail.setActionsTaken(actionTakenVOs);
307         return detail.build();
308     }
309 
310 }