001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kew.dto;
017
018 import com.google.common.base.Functions;
019 import com.google.common.base.Joiner;
020 import com.google.common.collect.Iterables;
021 import org.apache.commons.lang.StringUtils;
022 import org.apache.log4j.Logger;
023 import org.kuali.rice.core.api.exception.RiceRuntimeException;
024 import org.kuali.rice.core.api.reflect.DataDefinition;
025 import org.kuali.rice.core.api.reflect.ObjectDefinition;
026 import org.kuali.rice.core.api.reflect.PropertyDefinition;
027 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
028 import org.kuali.rice.core.api.uif.RemotableAttributeErrorContract;
029 import org.kuali.rice.core.api.util.xml.XmlHelper;
030 import org.kuali.rice.core.api.util.xml.XmlJotter;
031 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
032 import org.kuali.rice.kew.actiontaken.ActionTakenValue;
033 import org.kuali.rice.kew.api.KewApiServiceLocator;
034 import org.kuali.rice.kew.api.WorkflowRuntimeException;
035 import org.kuali.rice.kew.api.action.ActionRequest;
036 import org.kuali.rice.kew.api.action.ActionTaken;
037 import org.kuali.rice.kew.api.document.DocumentContentUpdate;
038 import org.kuali.rice.kew.api.document.DocumentDetail;
039 import org.kuali.rice.kew.api.document.InvalidDocumentContentException;
040 import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
041 import org.kuali.rice.kew.api.extension.ExtensionDefinition;
042 import org.kuali.rice.kew.api.extension.ExtensionUtils;
043 import org.kuali.rice.kew.definition.AttributeDefinition;
044 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
045 import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
046 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
047 import org.kuali.rice.kew.routeheader.StandardDocumentContent;
048 import org.kuali.rice.kew.rule.WorkflowRuleAttribute;
049 import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
050 import org.kuali.rice.kew.rule.XmlConfiguredAttribute;
051 import org.kuali.rice.kew.rule.bo.RuleAttribute;
052 import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
053 import org.kuali.rice.kew.service.KEWServiceLocator;
054 import org.kuali.rice.kew.api.KewApiConstants;
055 import org.w3c.dom.Document;
056 import org.w3c.dom.Element;
057 import org.w3c.dom.NodeList;
058 import org.xml.sax.SAXException;
059
060 import javax.xml.namespace.QName;
061 import javax.xml.parsers.DocumentBuilder;
062 import javax.xml.parsers.DocumentBuilderFactory;
063 import javax.xml.parsers.ParserConfigurationException;
064 import javax.xml.transform.TransformerException;
065 import java.io.IOException;
066 import java.util.ArrayList;
067 import java.util.HashMap;
068 import java.util.Iterator;
069 import java.util.List;
070 import java.util.Map;
071
072 /**
073 * Translates Workflow server side beans into client side VO beans.
074 *
075 * @author Kuali Rice Team (rice.collab@kuali.org)
076 */
077 public class DTOConverter {
078 private static final Logger LOG = Logger.getLogger(DTOConverter.class);
079
080 public static String buildUpdatedDocumentContent(String existingDocContent,
081 DocumentContentUpdate documentContentUpdate, String documentTypeName) {
082 if (existingDocContent == null) {
083 existingDocContent = KewApiConstants.DEFAULT_DOCUMENT_CONTENT;
084 }
085 String documentContent = KewApiConstants.DEFAULT_DOCUMENT_CONTENT;
086 StandardDocumentContent standardDocContent = new StandardDocumentContent(existingDocContent);
087 try {
088 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
089 Document document = builder.newDocument();
090 Element root = document.createElement(KewApiConstants.DOCUMENT_CONTENT_ELEMENT);
091 document.appendChild(root);
092 Element applicationContentElement = standardDocContent.getApplicationContent();
093 if (documentContentUpdate.getApplicationContent() != null) {
094 // application content has changed
095 if (!StringUtils.isEmpty(documentContentUpdate.getApplicationContent())) {
096 applicationContentElement = document.createElement(KewApiConstants.APPLICATION_CONTENT_ELEMENT);
097 XmlHelper.appendXml(applicationContentElement, documentContentUpdate.getApplicationContent());
098 } else {
099 // 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 }