1
2
3
4 package org.kuali.student.lum.workflow.node;
5
6 import java.util.ArrayList;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Set;
10
11 import javax.xml.namespace.QName;
12 import javax.xml.xpath.XPath;
13 import javax.xml.xpath.XPathConstants;
14 import javax.xml.xpath.XPathExpressionException;
15
16 import org.apache.commons.lang.StringUtils;
17
18 import org.kuali.rice.core.api.exception.RiceRuntimeException;
19 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
20 import org.kuali.rice.core.api.util.xml.XmlJotter;
21 import org.kuali.rice.kew.doctype.bo.DocumentType;
22 import org.kuali.rice.kew.engine.RouteContext;
23 import org.kuali.rice.kew.engine.RouteHelper;
24 import org.kuali.rice.kew.engine.node.Branch;
25 import org.kuali.rice.kew.engine.node.DynamicNode;
26 import org.kuali.rice.kew.engine.node.DynamicResult;
27 import org.kuali.rice.kew.engine.node.NodeState;
28 import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
29 import org.kuali.rice.kew.engine.node.RoleNode;
30 import org.kuali.rice.kew.engine.node.RouteNode;
31 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
32 import org.kuali.rice.kew.api.exception.WorkflowException;
33 import org.kuali.rice.kew.role.RoleRouteModule;
34 import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
35 import org.kuali.rice.kew.service.KEWServiceLocator;
36 import org.kuali.rice.kew.api.KewApiConstants;
37 import org.kuali.student.r2.common.dto.ContextInfo;
38 import org.kuali.student.r2.common.util.ContextUtils;
39 import org.kuali.student.r2.core.organization.dto.OrgInfo;
40 import org.kuali.student.r2.core.organization.dto.OrgOrgRelationInfo;
41 import org.kuali.student.r2.core.organization.service.OrganizationService;
42 import org.kuali.student.lum.workflow.qualifierresolver.AbstractOrganizationServiceQualifierResolver;
43 import org.kuali.student.lum.workflow.qualifierresolver.OrganizationCurriculumCommitteeQualifierResolver;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NodeList;
47
48
49
50
51
52
53
54 public class OrganizationDynamicNode implements DynamicNode {
55 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrganizationDynamicNode.class);
56
57
58 protected static final String ORG_HIERARCHY_NODE = "Org Hierarchy Review";
59
60 public static final String NODE_STATE_ORG_ID_KEY = "org.id.key";
61
62
63
64
65
66
67
68
69 public static final String QUALIFIER_RESOLVER_ELEMENT = "qualifierResolver";
70 public static final String QUALIFIER_RESOLVER_CLASS_ELEMENT = "qualifierResolverClass";
71 public static final String RESPONSIBILITY_TEMPLATE_NAME_ELEMENT = "responsibilityTemplateName";
72 public static final String NAMESPACE_ELEMENT = "namespace";
73
74 private OrganizationService organizationService;
75
76 public OrganizationService getOrganizationService() {
77 if (null == organizationService) {
78 organizationService = (OrganizationService) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/organization", "OrganizationService"));
79 }
80 return organizationService;
81 }
82
83 public void setOrganizationService(OrganizationService organizationService) {
84 this.organizationService = organizationService;
85 }
86
87
88 public DynamicResult transitioningInto(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteHelper helper, ContextInfo contextInfo) throws Exception {
89 LOG.debug("Entering transitioningInto");
90 DocumentType docType = setUpDocumentType(context.getDocument().getDocumentType(), dynamicNodeInstance);
91
92 String prototypeNodeName = ORG_HIERARCHY_NODE;
93 RouteNode roleNodePrototype = docType.getNamedProcess(prototypeNodeName).getInitialRouteNode();
94 if (roleNodePrototype == null) {
95 throw new WorkflowException("Couldn't locate node for name: " + prototypeNodeName);
96 }
97
98 List<String> orgIds = getInitialOrganizationIdsForRouting(context, dynamicNodeInstance, helper, contextInfo);
99 if ((orgIds != null) && (orgIds.size() > 1)) {
100 throw new RuntimeException("Found a total of " + orgIds.size() + " organizations for routing on document when only one is allowed.");
101 }
102 DynamicResult result = new DynamicResult(orgIds == null || orgIds.isEmpty(), null);
103 for (String orgId : orgIds) {
104 RouteNodeInstance nodeInstance = generateNextNodeInstance(orgId, roleNodePrototype, context, dynamicNodeInstance.getBranch(), helper);
105 LOG.debug("Exiting transitioningInto with " + ((nodeInstance == null) ? "no" : "a") + " valid next node instance");
106 if (nodeInstance != null) {
107 result.setNextNodeInstance(nodeInstance);
108 }
109 }
110 return result;
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 protected List<String> getInitialOrganizationIdsForRouting(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteHelper helper, ContextInfo contextInfo) {
129 List<String> orgIds = getOrganizationIdsFromDocumentContent(context);
130 if ((orgIds != null) && (orgIds.size() > 1)) {
131 throw new RuntimeException("Found a total of " + orgIds.size() + " organizations for routing on document when only one is allowed.");
132 }
133
134 try {
135 for (String orgId : orgIds) {
136 OrgInfo orgInfo = getOrganizationService().getOrg(orgId, contextInfo);
137 LOG.debug("Org on Document: " + getOrgInfoForPrint(orgInfo));
138 List<OrgOrgRelationInfo> orgRelationInfos = getOrganizationService().getOrgOrgRelationsByOrg(orgId, contextInfo);
139 for (OrgOrgRelationInfo orgOrgRelationInfo : orgRelationInfos) {
140 LOG.debug("---- Org Relation:");
141 LOG.debug("------------ Org ID: " + orgOrgRelationInfo.getOrgId());
142 orgInfo = getOrganizationService().getOrg(orgOrgRelationInfo.getRelatedOrgId(), contextInfo);
143 LOG.debug("------------ Related Org on Document: " + getOrgInfoForPrint(orgInfo));
144 LOG.debug("------------ Relation State: " + orgOrgRelationInfo.getStateKey());
145 LOG.debug("------------ Relation Type: " + orgOrgRelationInfo.getTypeKey());
146 }
147 List<OrgOrgRelationInfo> relatedOrgRelationInfos = null;
148 relatedOrgRelationInfos = getOrganizationService().getOrgOrgRelationsByOrg(orgId, contextInfo);
149 for (OrgOrgRelationInfo orgOrgRelationInfo : relatedOrgRelationInfos) {
150 LOG.debug("---- Related Org Relation:");
151 LOG.debug("------------ Related Org ID: " + orgOrgRelationInfo.getRelatedOrgId());
152 orgInfo = getOrganizationService().getOrg(orgOrgRelationInfo.getOrgId(), contextInfo);
153 LOG.debug("------------ Org of Relation: " + getOrgInfoForPrint(orgInfo));
154 LOG.debug("------------ Relation State: " + orgOrgRelationInfo.getStateKey());
155 LOG.debug("------------ Relation Type: " + orgOrgRelationInfo.getTypeKey());
156 }
157 }
158 } catch (Exception e) {
159 LOG.error(e);
160 throw new RuntimeException("Caught Exception using Organization Service", e);
161 }
162 return orgIds;
163 }
164
165
166
167
168
169
170
171
172
173 protected List<String> getOrganizationIdsFromDocumentContent(RouteContext context) {
174 Document xmlContent = context.getDocumentContent().getDocument();
175 XPath xPath = XPathHelper.newXPath();
176 try {
177 List<String> orgIds = new ArrayList<String>();
178 NodeList orgElements = (NodeList) xPath.evaluate("/documentContent/applicationContent/" + AbstractOrganizationServiceQualifierResolver.DOCUMENT_CONTENT_XML_ROOT_ELEMENT_NAME + "/orgId", xmlContent,
179 XPathConstants.NODESET);
180 for (int index = 0; index < orgElements.getLength(); index++) {
181 Element attributeElement = (Element) orgElements.item(index);
182 String attributeValue = attributeElement.getTextContent();
183 orgIds.add(attributeValue);
184 }
185 if (LOG.isDebugEnabled()) {
186 LOG.debug("Found " + orgElements.getLength() + " organization ids to parse for routing:" + XmlJotter.jotDocument(xmlContent));
187 }
188 return orgIds;
189 } catch (XPathExpressionException e) {
190 throw new RiceRuntimeException("Encountered an issue executing XPath.", e);
191 }
192 }
193
194 @Override
195 public DynamicResult transitioningInto(RouteContext routeContext, RouteNodeInstance routeNodeInstance, RouteHelper routeHelper) throws Exception {
196 return transitioningInto(routeContext, routeNodeInstance, routeHelper, ContextUtils.getContextInfo());
197 }
198
199 @Override
200 public DynamicResult transitioningOutOf(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
201 return transitioningOutOf( routeContext, routeHelper, ContextUtils.getContextInfo());
202 }
203
204
205 public DynamicResult transitioningOutOf(RouteContext context, RouteHelper helper, ContextInfo contextInfo) throws Exception {
206 LOG.debug("Variables for transitioningOutOf");
207 RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
208
209 List<String> relatedOrgIds = getNextOrganizationIdsForRouting(context, helper, contextInfo);
210
211 DynamicResult result = new DynamicResult(relatedOrgIds.isEmpty(), null);
212 for (String relatedOrgId : relatedOrgIds) {
213 RouteNodeInstance nodeInstance = generateNextNodeInstance(relatedOrgId, context, processInstance.getBranch(), helper);
214 result.getNextNodeInstances().add(nodeInstance);
215 }
216 return result;
217 }
218
219
220
221
222 protected String getOrgInfoForPrint(OrgInfo orgInfo) {
223 return orgInfo.getId() + " - " + orgInfo.getShortName() + " (" + orgInfo.getLongName() + ")";
224 }
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 protected List<String> getNextOrganizationIdsForRouting(RouteContext context, RouteHelper helper, ContextInfo contextInfo) {
244 RouteNodeInstance currentNode = context.getNodeInstance();
245 String currentNodeName = currentNode.getName();
246 LOG.debug("currentNodeName = '" + currentNodeName + "'");
247 NodeState currentNodeOrgIdState = currentNode.getNodeState(NODE_STATE_ORG_ID_KEY);
248 LOG.debug("currentNodeOrgIdState is " + ((currentNodeOrgIdState != null) ? "not " : "") + "null");
249 String currentNodeOrgId = (currentNodeOrgIdState != null) ? currentNodeOrgIdState.getValue() : null;
250 LOG.debug("currentNodeOrgId = '" + currentNodeOrgId + "'");
251 Set<String> relatedOrgIds = new HashSet<String>();
252 try {
253 List<OrgOrgRelationInfo> relatedOrgRelationInfos = null;
254 relatedOrgRelationInfos = getOrganizationService().getOrgOrgRelationsByOrg(currentNodeOrgId, contextInfo);
255 for (OrgOrgRelationInfo orgOrgRelationInfo : relatedOrgRelationInfos) {
256 if (StringUtils.equals("Active", orgOrgRelationInfo.getStateKey())) {
257 if (StringUtils.equals(AbstractOrganizationServiceQualifierResolver.KUALI_ORG_TYPE_CURRICULUM_PARENT, orgOrgRelationInfo.getTypeKey())) {
258 LOG.debug("---- Related Org Relation:");
259 OrgInfo referenceOrgInfo = null;
260 referenceOrgInfo = getOrganizationService().getOrg(orgOrgRelationInfo.getRelatedOrgId(), contextInfo);
261 OrgInfo nextNodeOrgInfo = null;
262 nextNodeOrgInfo = getOrganizationService().getOrg(orgOrgRelationInfo.getOrgId(), contextInfo);
263 LOG.debug("------------ Reference Org: " + getOrgInfoForPrint(referenceOrgInfo));
264 LOG.debug("------------ Org for Next Node: " + getOrgInfoForPrint(nextNodeOrgInfo));
265 relatedOrgIds.add(nextNodeOrgInfo.getId());
266 }
267 }
268 }
269 } catch (Exception e) {
270 LOG.error("Exception caught attempting to use org hierarchy routing", e);
271 throw new RuntimeException("Exception caught attempting to use org hierarchy routing", e);
272 }
273 return new ArrayList<String>(relatedOrgIds);
274 }
275
276
277
278
279
280
281 protected RouteNodeInstance generateNextNodeInstance(String orgId, RouteContext context, Branch branch, RouteHelper helper) {
282 return generateNextNodeInstance(orgId, helper.getNodeFactory().getRouteNode(context, ORG_HIERARCHY_NODE), context, branch, helper);
283 }
284
285
286
287
288
289 protected RouteNodeInstance generateNextNodeInstance(String orgId, RouteNode routeNodeDefinition, RouteContext context, Branch branch, RouteHelper helper) {
290 LOG.debug("Adding new node with name '" + routeNodeDefinition.getRouteNodeName() + "'");
291 RouteNodeInstance actualRouteNodeInstance = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), routeNodeDefinition);
292 actualRouteNodeInstance.setBranch(branch);
293 actualRouteNodeInstance.addNodeState(new NodeState(NODE_STATE_ORG_ID_KEY, orgId));
294 return actualRouteNodeInstance;
295 }
296
297
298
299
300
301
302
303
304
305
306
307 protected DocumentType setUpDocumentType(DocumentType documentType, RouteNodeInstance dynamicNodeInstance) {
308 boolean altered = false;
309
310 if (documentType.getNamedProcess(ORG_HIERARCHY_NODE) == null) {
311 RouteNode hierarchyNode = getKimRoleNode(ORG_HIERARCHY_NODE, dynamicNodeInstance);
312 documentType.addProcess(getPrototypeProcess(hierarchyNode, documentType));
313 altered = true;
314 }
315 if (altered) {
316
317 KEWServiceLocator.getDocumentTypeService().save(documentType);
318 }
319 return KEWServiceLocator.getDocumentTypeService().findByName(documentType.getName());
320 }
321
322
323
324
325
326
327
328 protected RouteNode getKimRoleNode(String routeNodeName, RouteNodeInstance dynamicNodeInstance) {
329 RouteNode roleNode = new RouteNode();
330 roleNode.setFinalApprovalInd(Boolean.FALSE);
331 roleNode.setMandatoryRouteInd(Boolean.FALSE);
332 roleNode.setActivationType(KewApiConstants.ROUTE_LEVEL_PARALLEL);
333 roleNode.setDocumentType(dynamicNodeInstance.getRouteNode().getDocumentType());
334 roleNode.setNodeType(RoleNode.class.getName());
335 roleNode.setRouteMethodName(RoleRouteModule.class.getName());
336 roleNode.setRouteMethodCode(KewApiConstants.ROUTE_LEVEL_ROUTE_MODULE);
337 roleNode.setRouteNodeName(routeNodeName);
338 roleNode.setContentFragment("<" + QUALIFIER_RESOLVER_CLASS_ELEMENT + ">" + OrganizationCurriculumCommitteeQualifierResolver.class.getName() + "</" + QUALIFIER_RESOLVER_CLASS_ELEMENT + ">");
339 return roleNode;
340 }
341
342 protected ProcessDefinitionBo getPrototypeProcess(RouteNode node, DocumentType documentType) {
343 ProcessDefinitionBo process = new ProcessDefinitionBo();
344 process.setDocumentType(documentType);
345 process.setInitial(false);
346 process.setInitialRouteNode(node);
347 process.setName(node.getRouteNodeName());
348 return process;
349 }
350
351 }