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 }