View Javadoc

1   /**
2    * Copyright 2005-2013 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(definition.getExtensionDefinition());
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         //KULRICE-7643
236         ExtensionDefinition extensionDefinition = null;
237         List<RuleAttribute> ruleAttribute = KEWServiceLocator.getRuleAttributeService().findByClassName(definition.getAttributeName());
238         if (ruleAttribute == null || ruleAttribute.isEmpty()) {
239             extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionByName(definition.getAttributeName());
240         }else{
241             //TODO: Should we do something more intelligent here? Rice 1.x returned only a single entry but we can now have a list
242             RuleAttribute tmpAttr = ruleAttribute.get(0);
243             extensionDefinition = RuleAttribute.to(tmpAttr);
244             if(ruleAttribute.size() > 1){
245                 LOG.warn("AttributeDefinition lookup (findByClassName) returned multiple attribute for the same class name. This should not happen, investigation recommended for classname: " 
246             + definition.getAttributeName() + " which has " + ruleAttribute.size() + " entries.");
247             }
248         }
249         
250         if (extensionDefinition == null) {
251             throw new WorkflowRuntimeException("Extension " + definition.getAttributeName() + " not found");
252         }
253         /*RuleAttribute ruleAttribute = KEWServiceLocator.getRuleAttributeService().findByName(definition.getAttributeName());
254         if (ruleAttribute == null) {
255             throw new WorkflowRuntimeException("Attribute " + definition.getAttributeName() + " not found");
256         }*/
257 
258         ObjectDefinition objectDefinition = new ObjectDefinition(extensionDefinition.getResourceDescriptor());
259         if (definition.getParameters() != null) {
260             for (String parameter : definition.getParameters()) {
261                 objectDefinition.addConstructorParameter(new DataDefinition(parameter, String.class));
262             }
263         }
264         boolean propertiesAsMap = KewApiConstants.RULE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType()) || KewApiConstants
265                 .SEARCHABLE_XML_ATTRIBUTE_TYPE.equals(extensionDefinition.getType());
266         if (!propertiesAsMap && definition.getPropertyDefinitions() != null) {
267             for (org.kuali.rice.kew.api.document.PropertyDefinition propertyDefinition : definition
268                     .getPropertyDefinitions()) {
269                 objectDefinition.addProperty(new PropertyDefinition(propertyDefinition.getName(), new DataDefinition(
270                         propertyDefinition.getValue(), String.class)));
271             }
272         }
273 
274         return new AttributeDefinition(extensionDefinition, objectDefinition);
275     }
276 
277     /**
278      * Interface for a simple service providing RouteNodeInstanceS based on their IDs
279      */
280     public static interface RouteNodeInstanceLoader {
281         RouteNodeInstance load(String routeNodeInstanceID);
282     }
283 
284 
285     public static DocumentDetail convertDocumentDetailNew(DocumentRouteHeaderValue routeHeader) {
286         if (routeHeader == null) {
287             return null;
288         }
289         org.kuali.rice.kew.api.document.Document document = DocumentRouteHeaderValue.to(routeHeader);
290         DocumentDetail.Builder detail = DocumentDetail.Builder.create(document);
291         Map<String, RouteNodeInstance> nodeInstances = new HashMap<String, RouteNodeInstance>();
292         List<ActionRequest> actionRequestVOs = new ArrayList<ActionRequest>();
293         List<ActionRequestValue> rootActionRequests = KEWServiceLocator.getActionRequestService().getRootRequests(
294                 routeHeader.getActionRequests());
295         for (Iterator<ActionRequestValue> iterator = rootActionRequests.iterator(); iterator.hasNext(); ) {
296             ActionRequestValue actionRequest = iterator.next();
297             actionRequestVOs.add(ActionRequestValue.to(actionRequest));
298             RouteNodeInstance nodeInstance = actionRequest.getNodeInstance();
299             if (nodeInstance == null) {
300                 continue;
301             }
302             if (nodeInstance.getRouteNodeInstanceId() == null) {
303                 throw new IllegalStateException(
304                         "Error creating document detail structure because of NULL node instance id.");
305             }
306             nodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
307         }
308         detail.setActionRequests(actionRequestVOs);
309         List<org.kuali.rice.kew.api.document.node.RouteNodeInstance> nodeInstanceVOs =
310                 new ArrayList<org.kuali.rice.kew.api.document.node.RouteNodeInstance>();
311         for (Iterator<RouteNodeInstance> iterator = nodeInstances.values().iterator(); iterator.hasNext(); ) {
312             RouteNodeInstance nodeInstance = iterator.next();
313             nodeInstanceVOs.add(RouteNodeInstance.to(nodeInstance));
314         }
315         detail.setRouteNodeInstances(nodeInstanceVOs);
316         List<ActionTaken> actionTakenVOs = new ArrayList<ActionTaken>();
317         for (Object element : routeHeader.getActionsTaken()) {
318             ActionTakenValue actionTaken = (ActionTakenValue) element;
319             actionTakenVOs.add(ActionTakenValue.to(actionTaken));
320         }
321         detail.setActionsTaken(actionTakenVOs);
322         return detail.build();
323     }
324 
325 }