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.krad.workflow; 017 018import java.util.ArrayList; 019import java.util.List; 020import java.util.Map; 021import java.util.Properties; 022 023import javax.xml.xpath.XPath; 024import javax.xml.xpath.XPathExpressionException; 025import javax.xml.xpath.XPathFactory; 026 027import org.apache.commons.lang.StringUtils; 028import org.kuali.rice.kew.engine.RouteContext; 029import org.kuali.rice.kew.rule.xmlrouting.WorkflowFunctionResolver; 030import org.kuali.rice.kew.rule.xmlrouting.WorkflowNamespaceContext; 031import org.kuali.rice.kns.util.FieldUtils; 032import org.kuali.rice.kns.web.ui.Field; 033import org.kuali.rice.kns.web.ui.Row; 034import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 035import org.kuali.rice.krad.util.KRADConstants; 036import org.kuali.rice.krad.util.KRADPropertyConstants; 037import org.kuali.rice.krad.util.UrlFactory; 038import org.w3c.dom.Document; 039 040 041public final class WorkflowUtils { 042 private static final String XPATH_ROUTE_CONTEXT_KEY = "_xpathKey"; 043 public static final String XSTREAM_SAFE_PREFIX = "wf:xstreamsafe('"; 044 public static final String XSTREAM_SAFE_SUFFIX = "')"; 045 public static final String XSTREAM_MATCH_ANYWHERE_PREFIX = "//"; 046 public static final String XSTREAM_MATCH_RELATIVE_PREFIX = "./"; 047 048 private WorkflowUtils() { 049 throw new UnsupportedOperationException("do not call"); 050 } 051 052 /** 053 * 054 * This method sets up the XPath with the correct workflow namespace and resolver initialized. This ensures that the XPath 055 * statements can use required workflow functions as part of the XPath statements. 056 * 057 * @param document - document 058 * @return a fully initialized XPath instance that has access to the workflow resolver and namespace. 059 * 060 */ 061 public final static XPath getXPath(Document document) { 062 XPath xpath = getXPath(RouteContext.getCurrentRouteContext()); 063 xpath.setNamespaceContext(new WorkflowNamespaceContext()); 064 WorkflowFunctionResolver resolver = new WorkflowFunctionResolver(); 065 resolver.setXpath(xpath); 066 resolver.setRootNode(document); 067 xpath.setXPathFunctionResolver(resolver); 068 return xpath; 069 } 070 071 public final static XPath getXPath(RouteContext routeContext) { 072 if (routeContext == null) { 073 return XPathFactory.newInstance().newXPath(); 074 } 075 if (!routeContext.getParameters().containsKey(XPATH_ROUTE_CONTEXT_KEY)) { 076 routeContext.getParameters().put(XPATH_ROUTE_CONTEXT_KEY, XPathFactory.newInstance().newXPath()); 077 } 078 return (XPath) routeContext.getParameters().get(XPATH_ROUTE_CONTEXT_KEY); 079 } 080 081 /** 082 * This method will do a simple XPath.evaluate, while wrapping your xpathExpression with the xstreamSafe function. It assumes a 083 * String result, and will return such. If an XPathExpressionException is thrown, this will be re-thrown within a 084 * RuntimeException. 085 * 086 * @param xpath A correctly initialized XPath instance. 087 * @param xpathExpression Your XPath Expression that needs to be wrapped in an xstreamSafe wrapper and run. 088 * @param item The document contents you will be searching within. 089 * @return The string value of the xpath.evaluate(). 090 */ 091 public static final String xstreamSafeEval(XPath xpath, String xpathExpression, Object item) { 092 String xstreamSafeXPath = xstreamSafeXPath(xpathExpression); 093 String evalResult = ""; 094 try { 095 evalResult = xpath.evaluate(xstreamSafeXPath, item); 096 } 097 catch (XPathExpressionException e) { 098 throw new RuntimeException("XPathExpressionException occurred on xpath: " + xstreamSafeXPath, e); 099 } 100 return evalResult; 101 } 102 103 /** 104 * This method wraps the passed-in XPath expression in XStream Safe wrappers, so that XStream generated reference links will be 105 * handled correctly. 106 * 107 * @param xpathExpression The XPath Expression you wish to use. 108 * @return Your XPath Expression wrapped in the XStreamSafe wrapper. 109 */ 110 public static final String xstreamSafeXPath(String xpathExpression) { 111 return new StringBuilder(XSTREAM_SAFE_PREFIX).append(xpathExpression).append(XSTREAM_SAFE_SUFFIX).toString(); 112 } 113 114 /** 115 * This method returns a label from the data dictionary service 116 * 117 * @param businessObjectClass - class where the label should come from 118 * @param attributeName - name of the attribute you need the label for 119 * @return the label from the data dictionary for the given Class and attributeName or null if not found 120 */ 121 public static final String getBusinessObjectAttributeLabel(Class businessObjectClass, String attributeName) { 122 return KRADServiceLocatorWeb.getDataDictionaryService().getAttributeLabel(businessObjectClass, attributeName); 123 } 124 125 126 /** 127 * This method builds a workflow-lookup-screen Row of type TEXT, with no quickfinder/lookup. 128 * 129 * @param propertyClass The Class of the BO that this row is based on. For example, Account.class for accountNumber. 130 * @param boPropertyName The property name on the BO that this row is based on. For example, accountNumber for 131 * Account.accountNumber. 132 * @param workflowPropertyKey The workflow-lookup-screen property key. For example, account_nbr for Account.accountNumber. This 133 * key can be anything, but needs to be consistent with what is used for the row/field key on the java attribute, so 134 * everything links up correctly. 135 * @return A populated and ready-to-use workflow lookupable.Row. 136 */ 137 public static Row buildTextRow(Class propertyClass, String boPropertyName, String workflowPropertyKey) { 138 if (propertyClass == null) { 139 throw new IllegalArgumentException("Method parameter 'propertyClass' was passed a NULL value."); 140 } 141 if (StringUtils.isBlank(boPropertyName)) { 142 throw new IllegalArgumentException("Method parameter 'boPropertyName' was passed a NULL or blank value."); 143 } 144 if (StringUtils.isBlank(workflowPropertyKey)) { 145 throw new IllegalArgumentException("Method parameter 'workflowPropertyKey' was passed a NULL or blank value."); 146 } 147 List<Field> fields = new ArrayList<Field>(); 148 Field field; 149 field = FieldUtils.getPropertyField(propertyClass, boPropertyName, false); 150 fields.add(field); 151 return new Row(fields); 152 } 153 154 /** 155 * This method builds a workflow-lookup-screen Row of type TEXT, with the attached lookup icon and functionality. 156 * 157 * @param propertyClass The Class of the BO that this row is based on. For example, Account.class for accountNumber. 158 * @param boPropertyName The property name on the BO that this row is based on. For example, accountNumber for 159 * Account.accountNumber. 160 * @param workflowPropertyKey The workflow-lookup-screen property key. For example, account_nbr for Account.accountNumber. This 161 * key can be anything, but needs to be consistent with what is used for the row/field key on the java attribute, so 162 * everything links up correctly. 163 * @return A populated and ready-to-use workflow lookupable.Row, which includes both the property field and the lookup icon. 164 */ 165 public static Row buildTextRowWithLookup(Class propertyClass, String boPropertyName, String workflowPropertyKey) { 166 return buildTextRowWithLookup(propertyClass, boPropertyName, workflowPropertyKey, null); 167 } 168 169 /** 170 * This method builds a workflow-lookup-screen Row of type TEXT, with the attached lookup icon and functionality. 171 * 172 * @param propertyClass The Class of the BO that this row is based on. For example, Account.class for accountNumber. 173 * @param boPropertyName The property name on the BO that this row is based on. For example, accountNumber for 174 * Account.accountNumber. 175 * @param workflowPropertyKey The workflow-lookup-screen property key. For example, account_nbr for Account.accountNumber. This 176 * key can be anything, but needs to be consistent with what is used for the row/field key on the java attribute, so 177 * everything links up correctly. 178 * @param fieldConversionsByBoPropertyName A list of extra field conversions where the key is the business object property name 179 * and the value is the workflow property key 180 * @return A populated and ready-to-use workflow lookupable.Row, which includes both the property field and the lookup icon. 181 */ 182 public static Row buildTextRowWithLookup(Class propertyClass, String boPropertyName, String workflowPropertyKey, Map fieldConversionsByBoPropertyName) { 183 if (propertyClass == null) { 184 throw new IllegalArgumentException("Method parameter 'propertyClass' was passed a NULL value."); 185 } 186 if (StringUtils.isBlank(boPropertyName)) { 187 throw new IllegalArgumentException("Method parameter 'boPropertyName' was passed a NULL or blank value."); 188 } 189 if (StringUtils.isBlank(workflowPropertyKey)) { 190 throw new IllegalArgumentException("Method parameter 'workflowPropertyKey' was passed a NULL or blank value."); 191 } 192 Field field; 193 field = FieldUtils.getPropertyField(propertyClass, boPropertyName, false); 194 195 List<Field> fields = new ArrayList<Field>(); 196 fields.add(field); 197 return new Row(fields); 198 } 199 200 /** 201 * This method builds a workflow-lookup-screen Row of type DROPDOWN. 202 * 203 * @param propertyClass The Class of the BO that this row is based on. For example, Account.class for accountNumber. 204 * @param boPropertyName The property name on the BO that this row is based on. For example, accountNumber for 205 * Account.accountNumber. 206 * @param workflowPropertyKey The workflow-lookup-screen property key. For example, account_nbr for Account.accountNumber. This 207 * key can be anything, but needs to be consistent with what is used for the row/field key on the java attribute, so 208 * everything links up correctly. 209 * @param optionMap The map of value, text pairs that will be used to constuct the dropdown list. 210 * @return A populated and ready-to-use workflow lookupable.Row. 211 */ 212 public static Row buildDropdownRow(Class propertyClass, String boPropertyName, String workflowPropertyKey, Map<String, String> optionMap, boolean addBlankRow) { 213 if (propertyClass == null) { 214 throw new IllegalArgumentException("Method parameter 'propertyClass' was passed a NULL value."); 215 } 216 if (StringUtils.isBlank(boPropertyName)) { 217 throw new IllegalArgumentException("Method parameter 'boPropertyName' was passed a NULL or blank value."); 218 } 219 if (StringUtils.isBlank(workflowPropertyKey)) { 220 throw new IllegalArgumentException("Method parameter 'workflowPropertyKey' was passed a NULL or blank value."); 221 } 222 if (optionMap == null) { 223 throw new IllegalArgumentException("Method parameter 'optionMap' was passed a NULL value."); 224 } 225 List<Field> fields = new ArrayList<Field>(); 226 Field field; 227 field = FieldUtils.getPropertyField(propertyClass, boPropertyName, false); 228 fields.add(field); 229 return new Row(fields); 230 } 231}