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 }