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