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.attribute;
017:
018: import java.util.ArrayList;
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026:
027: import javax.xml.xpath.XPath;
028: import javax.xml.xpath.XPathConstants;
029: import javax.xml.xpath.XPathExpressionException;
030:
031: import org.apache.commons.lang.StringUtils;
032: import org.apache.commons.lang.builder.EqualsBuilder;
033: import org.apache.commons.lang.builder.HashCodeBuilder;
034: import org.apache.log4j.Logger;
035: import org.kuali.core.lookup.LookupUtils;
036: import org.kuali.kfs.KFSConstants;
037: import org.kuali.kfs.context.SpringContext;
038: import org.kuali.module.cg.service.AwardService;
039: import org.kuali.module.chart.bo.Account;
040: import org.kuali.module.chart.bo.Chart;
041: import org.kuali.module.chart.service.AccountService;
042: import org.kuali.workflow.KualiWorkflowUtils;
043: import org.w3c.dom.Node;
044: import org.w3c.dom.NodeList;
045:
046: import edu.iu.uis.eden.WorkflowServiceErrorImpl;
047: import edu.iu.uis.eden.engine.RouteContext;
048: import edu.iu.uis.eden.exception.EdenUserNotFoundException;
049: import edu.iu.uis.eden.plugin.attributes.RoleAttribute;
050: import edu.iu.uis.eden.plugin.attributes.WorkflowAttribute;
051: import edu.iu.uis.eden.routeheader.DocumentContent;
052: import edu.iu.uis.eden.routetemplate.ResolvedQualifiedRole;
053: import edu.iu.uis.eden.routetemplate.Role;
054: import edu.iu.uis.eden.util.Utilities;
055: import edu.iu.uis.eden.workgroup.GroupId;
056: import edu.iu.uis.eden.workgroup.GroupNameId;
057:
058: public class KualiCGAttribute implements RoleAttribute,
059: WorkflowAttribute {
060:
061: static final long serialVersionUID = 101;
062:
063: private static final String FIN_COA_CD_KEY = "fin_coa_cd";
064:
065: private static final String ACCOUNT_NBR_KEY = "account_nbr";
066:
067: private static final String ROLE_STRING_DELIMITER = "~!~!~";
068:
069: private static Logger LOG = Logger
070: .getLogger(KualiCGAttribute.class);
071:
072: private boolean required;
073: private static XPath xpath;
074:
075: private String finCoaCd;
076: private String accountNbr;
077:
078: /**
079: * Constructs a KualiCGAttribute.java. This class tries to find an entry for a chart/account in the AwardAccount table. If it
080: * does, it look the Award up in the Award table and routes it to the workgroup listed.
081: */
082:
083: public KualiCGAttribute() {
084: }
085:
086: public KualiCGAttribute(String finCoaCd, String accountNbr) {
087: this .finCoaCd = finCoaCd;
088: this .accountNbr = accountNbr;
089:
090: }
091:
092: public List getRoutingDataRows() {
093: List rows = new ArrayList();
094: rows.add(KualiWorkflowUtils.buildTextRowWithLookup(Chart.class,
095: KFSConstants.CHART_OF_ACCOUNTS_CODE_PROPERTY_NAME,
096: FIN_COA_CD_KEY));
097: Map fieldConversionMap = new HashMap();
098: fieldConversionMap.put(
099: KFSConstants.CHART_OF_ACCOUNTS_CODE_PROPERTY_NAME,
100: FIN_COA_CD_KEY);
101: rows.add(KualiWorkflowUtils.buildTextRowWithLookup(
102: Account.class,
103: KFSConstants.ACCOUNT_NUMBER_PROPERTY_NAME,
104: ACCOUNT_NBR_KEY, fieldConversionMap));
105:
106: return rows;
107: }
108:
109: private String getQualifiedRoleString(AwardWorkgroupRole role) {
110: return new StringBuffer(getNullSafeValue(role.roleName))
111: .append(ROLE_STRING_DELIMITER).append(
112: getNullSafeValue(role.chart)).append(
113: ROLE_STRING_DELIMITER).append(
114: getNullSafeValue(role.accountNumber)).append(
115: ROLE_STRING_DELIMITER).append(
116: getNullSafeValue(role.routingWorkgroupName))
117: .toString();
118: }
119:
120: public List validateRoutingData(Map paramMap) {
121: List errors = new ArrayList();
122: this .finCoaCd = LookupUtils.forceUppercase(Account.class,
123: "chartOfAccountsCode", (String) paramMap
124: .get(FIN_COA_CD_KEY));
125: this .accountNbr = LookupUtils
126: .forceUppercase(Account.class, "accountNumber",
127: (String) paramMap.get(ACCOUNT_NBR_KEY));
128: validateAccount(errors);
129: return errors;
130: }
131:
132: private void validateAccount(List errors) {
133: if (StringUtils.isBlank(this .finCoaCd)
134: || StringUtils.isBlank(this .accountNbr)) {
135: errors.add(new WorkflowServiceErrorImpl(
136: "Account is required.",
137: "routetemplate.accountattribute.account.required"));
138: return;
139: }
140: Account account = SpringContext.getBean(AccountService.class)
141: .getByPrimaryIdWithCaching(finCoaCd, accountNbr);
142: if (account == null) {
143: errors.add(new WorkflowServiceErrorImpl(
144: "Account is invalid.",
145: "routetemplate.accountattribute.account.invalid"));
146: }
147: }
148:
149: public boolean isMatch(DocumentContent documentContent, List rules) {
150: return true;
151: }
152:
153: public String getDocContent() {
154: if (Utilities.isEmpty(getFinCoaCd())
155: || Utilities.isEmpty(getAccountNbr())) {
156: return "";
157: }
158: return new StringBuffer(
159: KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_PREFIX)
160: .append("<chart>")
161: .append(getFinCoaCd())
162: .append("</chart><accountNumber>")
163: .append(getAccountNbr())
164: .append("</accountNumber>")
165: .append(
166: KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_SUFFIX)
167: .toString();
168:
169: }
170:
171: public List getQualifiedRoleNames(String roleName,
172: DocumentContent docContent)
173: throws EdenUserNotFoundException {
174: Set awardWorkgroups = new HashSet();
175: XPath xpath = KualiWorkflowUtils.getXPath(docContent
176: .getDocument());
177: String docTypeName = docContent.getRouteContext().getDocument()
178: .getDocumentType().getName();
179: List qualifiedRoleNames = new ArrayList();
180: try {
181: if (((Boolean) xpath
182: .evaluate(
183: KualiWorkflowUtils
184: .xstreamSafeXPath(KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
185: + KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_XPATH_PREFIX),
186: docContent.getDocument(),
187: XPathConstants.BOOLEAN)).booleanValue()) {
188: String chart = xpath
189: .evaluate(
190: KualiWorkflowUtils
191: .xstreamSafeXPath(KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
192: + KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_XPATH_PREFIX
193: + "/chart"), docContent
194: .getDocument());
195: String accountNumber = xpath
196: .evaluate(
197: KualiWorkflowUtils
198: .xstreamSafeXPath(KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
199: + KualiWorkflowUtils.XML_REPORT_DOC_CONTENT_XPATH_PREFIX
200: + "/accountNumber"),
201: docContent.getDocument());
202: AwardWorkgroupRole role = new AwardWorkgroupRole(
203: roleName);
204: role.chart = chart;
205: role.accountNumber = accountNumber;
206: awardWorkgroups.add(role);
207: } else {
208: if (docTypeName
209: .equals(KualiWorkflowUtils.ACCOUNT_DOC_TYPE)) {
210: AwardWorkgroupRole role = new AwardWorkgroupRole(
211: roleName);
212: // An account doc should only have one chart/account, so no need to loop as in getAwardWorkgroupCriteria.
213: role.chart = xpath
214: .evaluate(
215: new StringBuffer(
216: KualiWorkflowUtils.XSTREAM_SAFE_PREFIX)
217: .append(
218: KualiWorkflowUtils.NEW_MAINTAINABLE_PREFIX)
219: .append(
220: KFSConstants.CHART_OF_ACCOUNTS_CODE_PROPERTY_NAME)
221: .append(
222: KualiWorkflowUtils.XSTREAM_SAFE_SUFFIX)
223: .toString(), docContent
224: .getDocument());
225: role.accountNumber = xpath
226: .evaluate(
227: new StringBuffer(
228: KualiWorkflowUtils.XSTREAM_SAFE_PREFIX)
229: .append(
230: KualiWorkflowUtils.NEW_MAINTAINABLE_PREFIX)
231: .append(
232: KFSConstants.ACCOUNT_NUMBER_PROPERTY_NAME)
233: .append(
234: KualiWorkflowUtils.XSTREAM_SAFE_SUFFIX)
235: .toString(), docContent
236: .getDocument());
237: Set tempSet = new HashSet();
238: tempSet.add(role);
239: awardWorkgroups.addAll(tempSet);
240: } else {
241: if (!KualiWorkflowUtils
242: .isTargetLineOnly(docTypeName)) {
243: NodeList sourceLineNodes = (NodeList) xpath
244: .evaluate(
245: KualiWorkflowUtils
246: .xstreamSafeXPath(KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
247: + KualiWorkflowUtils
248: .getSourceAccountingLineClassName(docTypeName)),
249: docContent.getDocument(),
250: XPathConstants.NODESET);
251: awardWorkgroups
252: .addAll(getAwardWorkgroupCriteria(
253: xpath, sourceLineNodes,
254: roleName));
255: }
256: if (!KualiWorkflowUtils
257: .isSourceLineOnly(docTypeName)) {
258: NodeList targetLineNodes = (NodeList) xpath
259: .evaluate(
260: KualiWorkflowUtils
261: .xstreamSafeXPath(KualiWorkflowUtils.XSTREAM_MATCH_ANYWHERE_PREFIX
262: + KualiWorkflowUtils
263: .getTargetAccountingLineClassName(docTypeName)),
264: docContent.getDocument(),
265: XPathConstants.NODESET);
266: awardWorkgroups
267: .addAll(getAwardWorkgroupCriteria(
268: xpath, targetLineNodes,
269: roleName));
270: }
271: }
272: }
273: for (Iterator iterator = awardWorkgroups.iterator(); iterator
274: .hasNext();) {
275: AwardWorkgroupRole role = (AwardWorkgroupRole) iterator
276: .next();
277: qualifiedRoleNames.add(getQualifiedRoleString(role));
278: }
279: } catch (Exception e) {
280: throw new RuntimeException(e);
281: }
282: return qualifiedRoleNames;
283: }
284:
285: private static Set getAwardWorkgroupCriteria(XPath xpath,
286: NodeList routingDataNodes, String roleName)
287: throws XPathExpressionException {
288: Set awardWorkgroups = new HashSet();
289: for (int i = 0; i < routingDataNodes.getLength(); i++) {
290: Node routingDataNode = routingDataNodes.item(i);
291: AwardWorkgroupRole role = new AwardWorkgroupRole(roleName);
292: role.chart = xpath
293: .evaluate(
294: KualiWorkflowUtils.XSTREAM_MATCH_RELATIVE_PREFIX
295: + KFSConstants.CHART_OF_ACCOUNTS_CODE_PROPERTY_NAME,
296: routingDataNode);
297: role.accountNumber = xpath
298: .evaluate(
299: KualiWorkflowUtils.XSTREAM_MATCH_RELATIVE_PREFIX
300: + KFSConstants.ACCOUNT_NUMBER_PROPERTY_NAME,
301: routingDataNode);
302:
303: awardWorkgroups.add(role);
304: }
305: return awardWorkgroups;
306: }
307:
308: public ResolvedQualifiedRole resolveQualifiedRole(
309: RouteContext context, String roleName, String qualifiedRole)
310: throws EdenUserNotFoundException {
311: try {
312: List members = new ArrayList();
313: String annotation = "";
314: AwardWorkgroupRole role = getUnqualifiedAwardWorkgroupRole(qualifiedRole);
315: annotation = (role.accountNumber == null ? ""
316: : "Routing to chart/account number " + role.chart
317: + "/" + role.accountNumber);
318: GroupId awardWorkgroupName = getAwardWorkgroupId(role);
319: if (awardWorkgroupName != null) {
320: members.add(awardWorkgroupName);
321: }
322: return new ResolvedQualifiedRole(roleName, members,
323: annotation);
324: } catch (Exception e) {
325: throw new RuntimeException(
326: "KualiCGAttribute encountered exception while attempting to resolve qualified role",
327: e);
328: }
329: }
330:
331: private static GroupId getAwardWorkgroupId(AwardWorkgroupRole role)
332: throws Exception {// fix this
333: String routingWorkgroupName = role.routingWorkgroupName;
334:
335: // if we dont have an ID, but we do have a chart/account, then hit Kuali to retrieve current FO
336:
337: if (StringUtils.isBlank(routingWorkgroupName)
338: && StringUtils.isNotBlank(role.chart)
339: && StringUtils.isNotBlank(role.accountNumber)) {
340: routingWorkgroupName = SpringContext.getBean(
341: AwardService.class).getAwardWorkgroupForAccount(
342: role.chart, role.accountNumber);
343: }
344:
345: // if we cant find a AwardWorkgroup, log it.
346: if (StringUtils.isBlank(routingWorkgroupName)) {
347: LOG
348: .debug(new StringBuffer(
349: "Could not locate the award workgroup for the given account ")
350: .append(role.accountNumber).toString());
351: return null;
352: }
353:
354: return new GroupNameId(routingWorkgroupName);
355:
356: }
357:
358: private static AwardWorkgroupRole getUnqualifiedAwardWorkgroupRole(
359: String qualifiedRole) {
360: String[] values = qualifiedRole
361: .split(ROLE_STRING_DELIMITER, -1);
362: if (values.length != 4) {
363: throw new RuntimeException(
364: "Invalid qualifiedRole, expected 4 encoded values: "
365: + qualifiedRole);
366: }
367: AwardWorkgroupRole role = new AwardWorkgroupRole(values[0]);
368: role.chart = getNullableString(values[1]);
369: role.accountNumber = getNullableString(values[2]);
370: role.routingWorkgroupName = getNullableString(values[3]);
371: return role;
372: }
373:
374: private static String getNullableString(String value) {
375: if (StringUtils.isEmpty(value)) {
376: return null;
377: }
378: return value;
379: }
380:
381: public List getRoleNames() {
382: List roles = new ArrayList();
383: roles.add(new Role(this .getClass(),
384: "CG_AWARD_FINANCIAL_ROUTING_WORKGROUP",
385: "C&G Award Financial Routing Workgroup"));
386:
387: return roles;
388: }
389:
390: public boolean isRequired() {
391: return required;
392: }
393:
394: private static String getNullSafeValue(String value) {
395: return (value == null ? "" : value);
396: }
397:
398: public List validateRuleData(Map paramMap) {
399: return Collections.EMPTY_LIST;
400: }
401:
402: public List getRuleExtensionValues() {
403: return Collections.EMPTY_LIST;
404: }
405:
406: public void setRequired(boolean required) {
407: this .required = required;
408: }
409:
410: public List getRuleRows() {
411: return Collections.EMPTY_LIST;
412: }
413:
414: public String getAccountNbr() {
415: return accountNbr;
416: }
417:
418: public void setAccountNbr(String accountNbr) {
419: this .accountNbr = accountNbr;
420: }
421:
422: public String getFinCoaCd() {
423: return finCoaCd;
424: }
425:
426: public void setFinCoaCd(String finCoaCd) {
427: this .finCoaCd = finCoaCd;
428: }
429:
430: public String getAttributeLabel() {
431: return "";
432: }
433:
434: private static class AwardWorkgroupRole {
435: public String roleName;
436:
437: public String chart;
438:
439: public String accountNumber;
440:
441: public String routingWorkgroupName;
442:
443: public AwardWorkgroupRole(String roleName) {
444: this .roleName = roleName;
445: }
446:
447: @Override
448: public boolean equals(Object object) {
449: if (object instanceof AwardWorkgroupRole) {
450: AwardWorkgroupRole role = (AwardWorkgroupRole) object;
451: return new EqualsBuilder().append(routingWorkgroupName,
452: role.routingWorkgroupName).append(chart,
453: role.chart).append(accountNumber,
454: role.accountNumber).isEquals();
455: }
456: return false;
457: }
458:
459: @Override
460: public int hashCode() {
461: return new HashCodeBuilder().append(routingWorkgroupName)
462: .append(chart).append(accountNumber).hashCode();
463: }
464:
465: }
466: }
|