View Javadoc

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