001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.kew.rule; 017 018import org.apache.commons.lang.StringUtils; 019import org.jdom.Document; 020import org.jdom.Element; 021import org.kuali.rice.core.api.uif.RemotableAttributeError; 022import org.kuali.rice.core.api.util.xml.XmlHelper; 023import org.kuali.rice.kew.api.WorkflowRuntimeException; 024import org.kuali.rice.kew.api.rule.RuleExtension; 025import org.kuali.rice.kew.doctype.bo.DocumentType; 026import org.kuali.rice.kew.doctype.service.DocumentTypeService; 027import org.kuali.rice.kew.exception.WorkflowServiceError; 028import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl; 029import org.kuali.rice.kew.routeheader.DocumentContent; 030import org.kuali.rice.kew.rule.xmlrouting.XPathHelper; 031import org.kuali.rice.kew.service.KEWServiceLocator; 032import org.kuali.rice.kns.web.ui.Field; 033import org.kuali.rice.kns.web.ui.Row; 034 035import javax.xml.xpath.XPath; 036import javax.xml.xpath.XPathExpressionException; 037import java.io.StringReader; 038import java.util.ArrayList; 039import java.util.Collection; 040import java.util.Iterator; 041import java.util.List; 042import java.util.Map; 043 044 045/** 046 * A {@link WorkflowRuleAttribute} which is used to route a rule based on the 047 * {@link DocumentType} of the rule which is created. 048 * 049 * @author Kuali Rice Team (rice.collab@kuali.org) 050 */ 051public class RuleRoutingAttribute implements WorkflowRuleAttribute { 052 053 private static final long serialVersionUID = -8884711461398770563L; 054 055 private static final String DOC_TYPE_NAME_PROPERTY = "docTypeFullName"; 056 private static final String DOC_TYPE_NAME_KEY = "docTypeFullName"; 057 058 private static final String LOOKUPABLE_CLASS = "org.kuali.rice.kew.doctype.bo.DocumentType"; 059 private static final String LOOUPABLE_FIELD = "name"; 060 private static final String DOC_TYPE_NAME_LABEL = "Document type name"; 061 062 private static final String DOC_TYPE_NAME_XPATH = "//newMaintainableObject/businessObject/docTypeName"; 063 private static final String DOC_TYPE_NAME_DEL_XPATH = "//newMaintainableObject/businessObject/delegationRule/docTypeName"; 064 065 private String doctypeName; 066 private List<Row> rows; 067 private boolean required; 068 069 public RuleRoutingAttribute(String docTypeName) { 070 this(); 071 setDoctypeName(docTypeName); 072 } 073 074 public RuleRoutingAttribute() { 075 buildRows(); 076 } 077 078 private void buildRows() { 079 rows = new ArrayList<Row>(); 080 081 List<Field> fields = new ArrayList<Field>(); 082 Field docTypeField = new Field(DOC_TYPE_NAME_LABEL, "", Field.TEXT, false, DOC_TYPE_NAME_PROPERTY, "", false, false, null, LOOKUPABLE_CLASS); 083 docTypeField.setFieldConversions(LOOUPABLE_FIELD + ":" + DOC_TYPE_NAME_PROPERTY); 084 fields.add(docTypeField); 085 086 rows.add(new Row(fields)); 087 } 088 089 @Override 090 public boolean isMatch(DocumentContent docContent, List<RuleExtension> ruleExtensions) { 091 setDoctypeName(getRuleDocumentTypeFromRuleExtensions(ruleExtensions)); 092 DocumentTypeService service = (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE); 093 094 try { 095 String docTypeName = getDocTypNameFromXML(docContent); 096 if (docTypeName.equals(getDoctypeName())) { 097 return true; 098 } 099 DocumentType documentType = service.findByName(docTypeName); 100 while (documentType != null && documentType.getParentDocType() != null) { 101 documentType = documentType.getParentDocType(); 102 if(documentType.getName().equals(getDoctypeName())){ 103 return true; 104 } 105 } 106 } catch (XPathExpressionException e) { 107 throw new WorkflowRuntimeException(e); 108 } 109 110 111 if (ruleExtensions.isEmpty()) { 112 return true; 113 } 114 return false; 115 } 116 117 protected String getRuleDocumentTypeFromRuleExtensions(List<RuleExtension> ruleExtensions) { 118 for (RuleExtension extension : ruleExtensions) { 119 if (extension.getRuleTemplateAttribute().getRuleAttribute().getResourceDescriptor().equals(getClass().getName())) { 120 for (Map.Entry<String, String> extensionValue : extension.getExtensionValuesMap().entrySet()) { 121 String key = extensionValue.getKey(); 122 String value = extensionValue.getValue(); 123 if (key.equals(DOC_TYPE_NAME_KEY)) { 124 return value; 125 } 126 } 127 } 128 } 129 return null; 130 } 131 132 @Override 133 public List getRuleRows() { 134 return rows; 135 } 136 137 @Override 138 public List getRoutingDataRows() { 139 return rows; 140 } 141 142 @Override 143 public String getDocContent() { 144 if (!org.apache.commons.lang.StringUtils.isEmpty(getDoctypeName())) { 145 return "<ruleRouting><doctype>" + getDoctypeName() + "</doctype></ruleRouting>"; 146 } else { 147 return ""; 148 } 149 } 150 151 152 private String getDocTypNameFromXML(DocumentContent docContent) throws XPathExpressionException { 153 XPath xPath = XPathHelper.newXPath(); 154 String docTypeName = xPath.evaluate(DOC_TYPE_NAME_XPATH, docContent.getDocument()); 155 156 if (StringUtils.isBlank(docTypeName)) { 157 docTypeName = xPath.evaluate(DOC_TYPE_NAME_DEL_XPATH, docContent.getDocument()); 158 159 if (StringUtils.isBlank(docTypeName)) { 160 throw new WorkflowRuntimeException("Could not locate Document Type Name on the document: " + 161 docContent.getRouteContext().getDocument().getDocumentId()); 162 } 163 } 164 return docTypeName; 165 } 166 167 168 public List<RuleRoutingAttribute> parseDocContent(DocumentContent docContent) { 169 try { 170 Document doc2 = (Document) XmlHelper.buildJDocument(new StringReader(docContent.getDocContent())); 171 172 List<RuleRoutingAttribute> doctypeAttributes = new ArrayList<RuleRoutingAttribute>(); 173 Collection<Element> ruleRoutings = XmlHelper.findElements(doc2.getRootElement(), "docTypeName"); 174 List<String> usedDTs = new ArrayList<String>(); 175 for (Iterator<Element> iter = ruleRoutings.iterator(); iter.hasNext();) { 176 Element ruleRoutingElement = (Element) iter.next(); 177 178 //Element docTypeElement = ruleRoutingElement.getChild("doctype"); 179 Element docTypeElement = ruleRoutingElement; 180 String elTxt = docTypeElement.getText(); 181 if (docTypeElement != null && !usedDTs.contains(elTxt)) { 182 usedDTs.add(elTxt); 183 doctypeAttributes.add(new RuleRoutingAttribute(elTxt)); 184 } 185 } 186 187 return doctypeAttributes; 188 } catch (Exception e) { 189 throw new RuntimeException(e); 190 } 191 } 192 193 @Override 194 public List getRuleExtensionValues() { 195 List extensions = new ArrayList(); 196 197 if (!org.apache.commons.lang.StringUtils.isEmpty(getDoctypeName())) { 198 RuleExtensionValue extension = new RuleExtensionValue(); 199 extension.setKey(DOC_TYPE_NAME_KEY); 200 extension.setValue(getDoctypeName()); 201 extensions.add(extension); 202 } 203 204 return extensions; 205 } 206 207 @Override 208 public List<RemotableAttributeError> validateRoutingData(Map paramMap) { 209 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(); 210 setDoctypeName((String) paramMap.get(DOC_TYPE_NAME_PROPERTY)); 211 if (isRequired() && org.apache.commons.lang.StringUtils.isEmpty(getDoctypeName())) { 212 errors.add(RemotableAttributeError.Builder.create("routetemplate.ruleroutingattribute.doctype.invalid", "doc type is not valid.").build()); 213 } 214 215 if (!org.apache.commons.lang.StringUtils.isEmpty(getDoctypeName())) { 216 DocumentTypeService service = (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE); 217 DocumentType documentType = service.findByName(getDoctypeName()); 218 if (documentType == null) { 219 errors.add(RemotableAttributeError.Builder.create("routetemplate.ruleroutingattribute.doctype.invalid", "doc type is not valid").build()); 220 } 221 } 222 return errors; 223 } 224 225 @Override 226 public List<RemotableAttributeError> validateRuleData(Map paramMap) { 227 return validateRoutingData(paramMap); 228 } 229 230 public String getDoctypeName() { 231 return this.doctypeName; 232 } 233 234 public void setDoctypeName(String docTypeName) { 235 this.doctypeName = docTypeName; 236 } 237 238 public void setRequired(boolean required) { 239 this.required = required; 240 } 241 242 public boolean isRequired() { 243 return required; 244 } 245}