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