View Javadoc
1   /**
2    * Copyright 2005-2016 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         }
128         return documentContent;
129     }
130 
131     private static Element createDocumentContentSection(Document document, Element existingAttributeElement,
132             List<WorkflowAttributeDefinition> definitions, String content, String elementName,
133             String documentTypeName) throws SAXException, IOException, ParserConfigurationException {
134         Element contentSectionElement = existingAttributeElement;
135         // if they've updated the content, we're going to re-build the content section element from scratch
136         if (content != null) {
137             if (!org.apache.commons.lang.StringUtils.isEmpty(content)) {
138                 contentSectionElement = document.createElement(elementName);
139                 // if they didn't merely clear the content, let's build the content section element by combining the children
140                 // of the incoming XML content
141                 Element incomingAttributeElement = XmlHelper.readXml(content).getDocumentElement();
142                 NodeList children = incomingAttributeElement.getChildNodes();
143                 for (int index = 0; index < children.getLength(); index++) {
144                     contentSectionElement.appendChild(document.importNode(children.item(index), true));
145                 }
146             } else {
147                 contentSectionElement = null;
148             }
149         }
150         // if they have new definitions we're going to append those to the existing content section
151         if (definitions != null && !definitions.isEmpty()) {
152             String errorMessage = "";
153             boolean inError = false;
154             if (contentSectionElement == null) {
155                 contentSectionElement = document.createElement(elementName);
156             }
157             for (WorkflowAttributeDefinition definitionVO : definitions) {
158                 AttributeDefinition definition = convertWorkflowAttributeDefinition(definitionVO);
159                 ExtensionDefinition extensionDefinition = definition.getExtensionDefinition();
160 
161                 Object attribute = null;
162                 attribute = GlobalResourceLoader.getObject(definition.getObjectDefinition());
163                 if (attribute == null) {
164                     attribute = GlobalResourceLoader.getService(QName.valueOf(
165                             definition.getExtensionDefinition().getResourceDescriptor()));
166                 }
167 
168                 if (attribute instanceof XmlConfiguredAttribute) {
169                     ((XmlConfiguredAttribute)attribute).setExtensionDefinition(definition.getExtensionDefinition());
170                 }
171                 boolean propertiesAsMap = false;
172                 if (KewApiConstants.RULE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType())) {
173                     propertiesAsMap = true;
174                 }
175                 if (propertiesAsMap) {
176                     for (org.kuali.rice.kew.api.document.PropertyDefinition propertyDefinitionVO : definitionVO
177                             .getPropertyDefinitions()) {
178                         if (attribute instanceof GenericXMLRuleAttribute) {
179                             ((GenericXMLRuleAttribute) attribute).getParamMap().put(propertyDefinitionVO.getName(),
180                                     propertyDefinitionVO.getValue());
181                         }
182                     }
183                 }
184 
185                 // validate inputs from client application if the attribute is capable
186                 if (attribute instanceof WorkflowAttributeXmlValidator) {
187                     List<? extends RemotableAttributeErrorContract> errors =
188                             ((WorkflowAttributeXmlValidator) attribute).validateClientRoutingData();
189                     if (!errors.isEmpty()) {
190                         inError = true;
191                         errorMessage += "Error validating attribute " + definitionVO.getAttributeName() + " ";
192                         errorMessage += Joiner.on("; ").join(Iterables.transform(errors, Functions.toStringFunction()));
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         //KULRICE-7643
234         ExtensionDefinition extensionDefinition = null;
235         List<RuleAttribute> ruleAttribute = KEWServiceLocator.getRuleAttributeService().findByClassName(definition.getAttributeName());
236         if (ruleAttribute == null || ruleAttribute.isEmpty()) {
237             extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionByName(definition.getAttributeName());
238         }else{
239             //TODO: Should we do something more intelligent here? Rice 1.x returned only a single entry but we can now have a list
240             RuleAttribute tmpAttr = ruleAttribute.get(0);
241             extensionDefinition = RuleAttribute.to(tmpAttr);
242             if(ruleAttribute.size() > 1){
243                 LOG.warn("AttributeDefinition lookup (findByClassName) returned multiple attribute for the same class name. This should not happen, investigation recommended for classname: " 
244             + definition.getAttributeName() + " which has " + ruleAttribute.size() + " entries.");
245             }
246         }
247         
248         if (extensionDefinition == null) {
249             throw new WorkflowRuntimeException("Extension " + definition.getAttributeName() + " not found");
250         }
251         /*RuleAttribute ruleAttribute = KEWServiceLocator.getRuleAttributeService().findByName(definition.getAttributeName());
252         if (ruleAttribute == null) {
253             throw new WorkflowRuntimeException("Attribute " + definition.getAttributeName() + " not found");
254         }*/
255 
256         ObjectDefinition objectDefinition = new ObjectDefinition(extensionDefinition.getResourceDescriptor());
257         if (definition.getParameters() != null) {
258             for (String parameter : definition.getParameters()) {
259                 objectDefinition.addConstructorParameter(new DataDefinition(parameter, String.class));
260             }
261         }
262         boolean propertiesAsMap = KewApiConstants.RULE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType()) || KewApiConstants
263                 .SEARCHABLE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType());
264         if (!propertiesAsMap && definition.getPropertyDefinitions() != null) {
265             for (org.kuali.rice.kew.api.document.PropertyDefinition propertyDefinition : definition
266                     .getPropertyDefinitions()) {
267                 objectDefinition.addProperty(new PropertyDefinition(propertyDefinition.getName(), new DataDefinition(
268                         propertyDefinition.getValue(), String.class)));
269             }
270         }
271 
272         return new AttributeDefinition(extensionDefinition, objectDefinition);
273     }
274 
275     /**
276      * Interface for a simple service providing RouteNodeInstanceS based on their IDs
277      */
278     public static interface RouteNodeInstanceLoader {
279         RouteNodeInstance load(String routeNodeInstanceID);
280     }
281 
282 
283     public static DocumentDetail convertDocumentDetailNew(DocumentRouteHeaderValue routeHeader) {
284         if (routeHeader == null) {
285             return null;
286         }
287         org.kuali.rice.kew.api.document.Document document = DocumentRouteHeaderValue.to(routeHeader);
288         DocumentDetail.Builder detail = DocumentDetail.Builder.create(document);
289         Map<String, RouteNodeInstance> nodeInstances = new HashMap<String, RouteNodeInstance>();
290         List<ActionRequest> actionRequestVOs = new ArrayList<ActionRequest>();
291         List<ActionRequestValue> rootActionRequests = KEWServiceLocator.getActionRequestService().getRootRequests(
292                 routeHeader.getActionRequests());
293         for (Iterator<ActionRequestValue> iterator = rootActionRequests.iterator(); iterator.hasNext(); ) {
294             ActionRequestValue actionRequest = iterator.next();
295             actionRequestVOs.add(ActionRequestValue.to(actionRequest));
296             RouteNodeInstance nodeInstance = actionRequest.getNodeInstance();
297             if (nodeInstance == null) {
298                 continue;
299             }
300             if (nodeInstance.getRouteNodeInstanceId() == null) {
301                 throw new IllegalStateException(
302                         "Error creating document detail structure because of NULL node instance id.");
303             }
304             nodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
305         }
306         detail.setActionRequests(actionRequestVOs);
307         List<org.kuali.rice.kew.api.document.node.RouteNodeInstance> nodeInstanceVOs =
308                 new ArrayList<org.kuali.rice.kew.api.document.node.RouteNodeInstance>();
309         for (Iterator<RouteNodeInstance> iterator = nodeInstances.values().iterator(); iterator.hasNext(); ) {
310             RouteNodeInstance nodeInstance = iterator.next();
311             nodeInstanceVOs.add(RouteNodeInstance.to(nodeInstance));
312         }
313         detail.setRouteNodeInstances(nodeInstanceVOs);
314         List<ActionTaken> actionTakenVOs = new ArrayList<ActionTaken>();
315         for (Object element : routeHeader.getActionsTaken()) {
316             ActionTakenValue actionTaken = (ActionTakenValue) element;
317             actionTakenVOs.add(ActionTakenValue.to(actionTaken));
318         }
319         detail.setActionsTaken(actionTakenVOs);
320         return detail.build();
321     }
322 
323 }