001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.workflow.module.purap.attribute;
017:
018: import java.util.ArrayList;
019: import java.util.Iterator;
020: import java.util.List;
021: import java.util.Map;
022:
023: import javax.xml.xpath.XPath;
024: import javax.xml.xpath.XPathConstants;
025: import javax.xml.xpath.XPathExpressionException;
026:
027: import org.apache.commons.lang.StringUtils;
028: import org.apache.log4j.Logger;
029: import org.kuali.core.lookup.LookupUtils;
030: import org.kuali.core.service.DocumentService;
031: import org.kuali.kfs.KFSConstants;
032: import org.kuali.kfs.KFSPropertyConstants;
033: import org.kuali.kfs.bo.Options;
034: import org.kuali.kfs.context.SpringContext;
035: import org.kuali.kfs.service.OptionsService;
036: import org.kuali.module.chart.bo.Chart;
037: import org.kuali.module.chart.service.ChartService;
038: import org.kuali.module.financial.service.UniversityDateService;
039: import org.kuali.module.gl.service.SufficientFundsService;
040: import org.kuali.module.gl.util.SufficientFundsItem;
041: import org.kuali.module.purap.document.PurchaseOrderDocument;
042: import org.kuali.workflow.KualiWorkflowUtils;
043:
044: import edu.iu.uis.eden.WorkflowServiceErrorImpl;
045: import edu.iu.uis.eden.exception.WorkflowException;
046: import edu.iu.uis.eden.lookupable.Row;
047: import edu.iu.uis.eden.plugin.attributes.WorkflowAttribute;
048: import edu.iu.uis.eden.routeheader.DocumentContent;
049: import edu.iu.uis.eden.routetemplate.RuleExtension;
050: import edu.iu.uis.eden.routetemplate.RuleExtensionValue;
051:
052: /**
053: * TODO delyea - documentation
054: */
055: public class KualiPurchaseOrderBudgetAttribute implements
056: WorkflowAttribute {
057: private static Logger LOG = Logger
058: .getLogger(KualiPurchaseOrderBudgetAttribute.class);
059:
060: public static final String FIN_COA_CD_KEY = "fin_coa_cd";
061: private static final String UNIVERSITY_FISCAL_YEAR_KEY = "univ_fiscal_year";
062:
063: private boolean required = false;
064: private String finCoaCd;
065: private String fiscalYear;
066: private List ruleRows;
067: private List routingDataRows;
068:
069: /**
070: * No arg constructor
071: */
072: public KualiPurchaseOrderBudgetAttribute() {
073: ruleRows = new ArrayList<edu.iu.uis.eden.lookupable.Row>();
074: ruleRows.add(KualiWorkflowUtils.buildTextRowWithLookup(
075: Chart.class,
076: KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE,
077: FIN_COA_CD_KEY));
078:
079: routingDataRows = new ArrayList<edu.iu.uis.eden.lookupable.Row>();
080: routingDataRows.add(KualiWorkflowUtils.buildTextRowWithLookup(
081: Options.class,
082: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
083: UNIVERSITY_FISCAL_YEAR_KEY));
084: routingDataRows.add(KualiWorkflowUtils.buildTextRowWithLookup(
085: Chart.class,
086: KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE,
087: FIN_COA_CD_KEY));
088: }
089:
090: /**
091: * Constructs a new object given the chart code
092: */
093: public KualiPurchaseOrderBudgetAttribute(String finCoaCode) {
094: this ();
095: this .finCoaCd = finCoaCode;
096: }
097:
098: /**
099: * @see edu.iu.uis.eden.plugin.attributes.WorkflowAttribute#getDocContent()
100: */
101: public String getDocContent() {
102: if ((StringUtils.isBlank(getFinCoaCd()))
103: && (StringUtils.isBlank(getFiscalYear()))) {
104: return "";
105: }
106: StringBuffer returnValue = new StringBuffer(
107: KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_PREFIX);
108: returnValue
109: .append(
110: "<"
111: + KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR
112: + ">")
113: .append(getFiscalYear())
114: .append(
115: "</"
116: + KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR
117: + ">");
118: returnValue
119: .append(
120: "<"
121: + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE
122: + ">")
123: .append(getFinCoaCd())
124: .append(
125: "</"
126: + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE
127: + ">");
128: return returnValue.append(
129: KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_SUFFIX)
130: .toString();
131: }
132:
133: /**
134: * @see edu.iu.uis.eden.plugin.attributes.WorkflowAttribute#getRoutingDataRows()
135: */
136: public List<Row> getRoutingDataRows() {
137: return routingDataRows;
138: }
139:
140: /**
141: * @see edu.iu.uis.eden.plugin.attributes.WorkflowAttribute#getRuleRows()
142: */
143: public List<Row> getRuleRows() {
144: return ruleRows;
145: }
146:
147: /**
148: * @see edu.iu.uis.eden.plugin.attributes.WorkflowAttribute#getRuleExtensionValues()
149: */
150: public List<RuleExtensionValue> getRuleExtensionValues() {
151: List extensions = new ArrayList();
152: extensions.add(new RuleExtensionValue(FIN_COA_CD_KEY,
153: getFinCoaCd()));
154: return extensions;
155: }
156:
157: /**
158: * This method will return true for a match if all the following conditions are met:
159: * <ol>
160: * <li>The fiscal year of the document is less than or equal to the system's current fiscal year
161: * <li>At least one account with the rule extension chart code (passed in via the ruleExtensions parameter) has insufficient
162: * funds on it
163: * </ol>
164: *
165: * @param docContent - contains the data in XML format that will be compared with the rules saved in workflow
166: * @param ruleExtensions - These should include chart codes to match against coming from rules in the system
167: * @return true if this document contains required criteria and rule should fire
168: */
169: public boolean isMatch(DocumentContent docContent,
170: List<RuleExtension> ruleExtensions) {
171: boolean alwaysRoutes = false;
172: String documentHeaderId = null;
173: String currentXpathExpression = null;
174: try {
175: String ruleChartCode = getRuleExtentionValue(
176: FIN_COA_CD_KEY, ruleExtensions);
177: if ((StringUtils.isBlank(ruleChartCode))
178: || (KFSConstants.WILDCARD_CHARACTER
179: .equalsIgnoreCase(ruleChartCode))) {
180: // if rule extension is blank or the Wildcard character... always match this rule if criteria is true
181: alwaysRoutes = true;
182: // String errorMsg = "Attempted matching of Rule Extension where " +
183: // KualiWorkflowUtils.getBusinessObjectAttributeLabel(Chart.class, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE) + "
184: // is blank but is required";
185: // LOG.error(errorMsg);
186: // throw new RuntimeException(errorMsg);
187: }
188: XPath xPath = KualiWorkflowUtils.getXPath(docContent
189: .getDocument());
190: currentXpathExpression = KualiWorkflowUtils
191: .xstreamSafeXPath(KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
192: + KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_XPATH_PREFIX);
193: boolean isReport = ((Boolean) xPath.evaluate(
194: currentXpathExpression, docContent.getDocument(),
195: XPathConstants.BOOLEAN)).booleanValue();
196: if (isReport) {
197: currentXpathExpression = KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
198: + KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_XPATH_PREFIX
199: + "/"
200: + KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR;
201: } else {
202: currentXpathExpression = KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
203: + "document/postingYear";
204: }
205: String documentFiscalYearString = KualiWorkflowUtils
206: .xstreamSafeEval(xPath, currentXpathExpression,
207: docContent.getDocContent());
208: // if document's fiscal year is less than or equal to the current fiscal year
209: if (SpringContext.getBean(UniversityDateService.class)
210: .getCurrentFiscalYear().compareTo(
211: Integer.valueOf(documentFiscalYearString)) >= 0) {
212: if (alwaysRoutes) {
213: return true;
214: }
215: if (isReport) {
216: currentXpathExpression = KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
217: + KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_XPATH_PREFIX
218: + "/"
219: + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE;
220: return ruleChartCode
221: .equalsIgnoreCase(KualiWorkflowUtils
222: .xstreamSafeEval(xPath,
223: currentXpathExpression,
224: docContent.getDocContent()));
225: } else {
226: documentHeaderId = docContent.getRouteContext()
227: .getDocument().getRouteHeaderId()
228: .toString();
229: PurchaseOrderDocument po = (PurchaseOrderDocument) SpringContext
230: .getBean(DocumentService.class)
231: .getByDocumentHeaderId(documentHeaderId);
232: // get list of sufficientfundItems
233: List<SufficientFundsItem> fundsItems = SpringContext
234: .getBean(SufficientFundsService.class)
235: .checkSufficientFunds(
236: po
237: .getPendingLedgerEntriesForSufficientFundsChecking());
238: for (SufficientFundsItem fundsItem : fundsItems) {
239: if (ruleChartCode.equalsIgnoreCase(fundsItem
240: .getAccount().getChartOfAccountsCode())) {
241: LOG
242: .debug("Chart code of rule extension matches chart code of at least one Sufficient Funds Item");
243: return true;
244: }
245: }
246: }
247: }
248: } catch (WorkflowException we) {
249: String errorMsg = "Error attempted to get document using doc id "
250: + documentHeaderId;
251: LOG.error(errorMsg, we);
252: throw new RuntimeException(errorMsg, we);
253: } catch (XPathExpressionException xee) {
254: String errorMsg = "Error trying to use xpath expression "
255: + currentXpathExpression;
256: LOG.error(errorMsg, xee);
257: throw new RuntimeException(errorMsg, xee);
258: }
259: return false;
260: }
261:
262: private String getRuleExtentionValue(String key, List ruleExtensions) {
263: for (Iterator iter = ruleExtensions.iterator(); iter.hasNext();) {
264: RuleExtension extension = (RuleExtension) iter.next();
265: if (extension.getRuleTemplateAttribute().getRuleAttribute()
266: .getClassName().equals(this .getClass().getName())) {
267: for (Iterator iterator = extension.getExtensionValues()
268: .iterator(); iterator.hasNext();) {
269: RuleExtensionValue value = (RuleExtensionValue) iterator
270: .next();
271: if (value.getKey().equals(key)) {
272: return value.getValue();
273: }
274: }
275: }
276: }
277: return null;
278: }
279:
280: /**
281: * @see edu.iu.uis.eden.plugin.attributes.WorkflowAttribute#validateRoutingData(java.util.Map)
282: */
283: public List validateRoutingData(Map paramMap) {
284: List errors = new ArrayList();
285: setFiscalYear(LookupUtils.forceUppercase(Options.class,
286: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
287: (String) paramMap.get(UNIVERSITY_FISCAL_YEAR_KEY)));
288: setFinCoaCd(LookupUtils.forceUppercase(Chart.class,
289: KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE,
290: (String) paramMap.get(FIN_COA_CD_KEY)));
291: String label = KualiWorkflowUtils
292: .getBusinessObjectAttributeLabel(Chart.class,
293: KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
294: if (StringUtils.isBlank(getFinCoaCd())) {
295: String errorMessage = label + " is required";
296: errors.add(new WorkflowServiceErrorImpl(errorMessage,
297: "routetemplate.xmlattribute.error", errorMessage));
298: } else {
299: // not blank so check value for validity
300: Chart chart = SpringContext.getBean(ChartService.class)
301: .getByPrimaryId(getFinCoaCd());
302: if (chart == null) {
303: String errorMessage = label + " entered is invalid";
304: errors.add(new WorkflowServiceErrorImpl(errorMessage,
305: "routetemplate.xmlattribute.error",
306: errorMessage));
307: }
308: }
309: label = KualiWorkflowUtils.getBusinessObjectAttributeLabel(
310: Options.class,
311: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR);
312: if (StringUtils.isBlank(getFiscalYear())) {
313: String errorMessage = label + " is required";
314: errors.add(new WorkflowServiceErrorImpl(errorMessage,
315: "routetemplate.xmlattribute.error", errorMessage));
316: } else {
317: // not blank so check value for validity
318: Options options = SpringContext.getBean(
319: OptionsService.class).getOptions(
320: Integer.valueOf(getFiscalYear()));
321: if (options == null) {
322: String errorMessage = label + " entered is invalid";
323: errors.add(new WorkflowServiceErrorImpl(errorMessage,
324: "routetemplate.xmlattribute.error",
325: errorMessage));
326: }
327: }
328: return errors;
329: }
330:
331: /**
332: * @see edu.iu.uis.eden.plugin.attributes.WorkflowAttribute#validateRuleData(java.util.Map)
333: */
334: public List validateRuleData(Map paramMap) {
335: List errors = new ArrayList();
336: setFinCoaCd(LookupUtils.forceUppercase(Chart.class,
337: KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE,
338: (String) paramMap.get(FIN_COA_CD_KEY)));
339: String label = KualiWorkflowUtils
340: .getBusinessObjectAttributeLabel(Chart.class,
341: KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
342: if (isRequired() && StringUtils.isBlank(getFinCoaCd())) {
343: // value is blank but required
344: String errorMessage = label + " is required";
345: errors.add(new WorkflowServiceErrorImpl(errorMessage,
346: "routetemplate.xmlattribute.error", errorMessage));
347: } else if (StringUtils.isNotBlank(getFinCoaCd())) {
348: // not blank so check value for validity
349: Chart chart = SpringContext.getBean(ChartService.class)
350: .getByPrimaryId(getFinCoaCd());
351: if (chart == null) {
352: String errorMessage = label + " entered is invalid";
353: errors.add(new WorkflowServiceErrorImpl(errorMessage,
354: "routetemplate.xmlattribute.error",
355: errorMessage));
356: }
357: }
358: return errors;
359: }
360:
361: /**
362: * Gets the required attribute.
363: *
364: * @return Returns the required.
365: */
366: public boolean isRequired() {
367: return required;
368: }
369:
370: /**
371: * Sets the required attribute value.
372: *
373: * @param required The required to set.
374: */
375: public void setRequired(boolean required) {
376: this .required = required;
377: }
378:
379: /**
380: * Gets the finCoaCd attribute.
381: *
382: * @return Returns the finCoaCd.
383: */
384: public String getFinCoaCd() {
385: return finCoaCd;
386: }
387:
388: /**
389: * Sets the finCoaCd attribute value.
390: *
391: * @param finCoaCd The finCoaCd to set.
392: */
393: public void setFinCoaCd(String finCoaCd) {
394: this .finCoaCd = finCoaCd;
395: }
396:
397: /**
398: * Gets the fiscalYear attribute.
399: *
400: * @return Returns the fiscalYear.
401: */
402: public String getFiscalYear() {
403: return fiscalYear;
404: }
405:
406: /**
407: * Sets the fiscalYear attribute value.
408: *
409: * @param fiscalYear The fiscalYear to set.
410: */
411: public void setFiscalYear(String fiscalYear) {
412: this.fiscalYear = fiscalYear;
413: }
414: }
|