Clover Coverage Report - KS LUM 1.1.0-M10-SNAPSHOT (Aggregated)
Coverage timestamp: Fri Dec 17 2010 06:40:47 EST
../../../../../../img/srcFileCovDistChart0.png 54% of files have more coverage
122   336   32   9.38
28   221   0.26   13
13     2.46  
1    
 
  OrganizationDynamicNode       Line # 51 122 0% 32 163 0% 0.0
 
No Tests
 
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.AbstractCocOrgQualifierResolver;
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    * A Dynamic Node implementation that will use the KS Organization Hierarchy to
47    * dynamically generate route paths based on the organizations sent to Workflow
48    * for each document
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    // name of the prototype node used by the dynamically created node instances
55    protected static final String ORG_HIERARCHY_NODE = "Org Hierarchy Review";
56    // node state key that will be used to store the organization ids in each node instance
57    public static final String NODE_STATE_ORG_ID_KEY = "org.id.key";
58   
59    /**
60    * The following 4 properties should match the RoleRouteModule constants
61    * which are currently set to 'protected'
62    *
63    * Created https://jira.kuali.org/browse/KULRICE-4448 to track change to
64    * Rice
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  0 toggle public OrganizationService getOrganizationService() {
74  0 if (null == organizationService) {
75  0 organizationService = (OrganizationService) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/organization", "OrganizationService"));
76    }
77  0 return organizationService;
78    }
79   
 
80  0 toggle public void setOrganizationService(OrganizationService organizationService) {
81  0 this.organizationService = organizationService;
82    }
83   
 
84  0 toggle @Override
85    public DynamicResult transitioningInto(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteHelper helper) throws Exception {
86  0 LOG.debug("Entering transitioningInto");
87  0 DocumentType docType = setUpDocumentType(context.getDocument().getDocumentType(), dynamicNodeInstance);
88    // String prototypeNodeName = RouteNodes.DEPT.getNodeName();
89  0 String prototypeNodeName = ORG_HIERARCHY_NODE;
90  0 RouteNode roleNodePrototype = docType.getNamedProcess(prototypeNodeName).getInitialRouteNode();
91  0 if (roleNodePrototype == null) {
92  0 throw new WorkflowException("Couldn't locate node for name: " + prototypeNodeName);
93    }
94   
95  0 List<String> orgIds = getInitialOrganizationIdsForRouting(context, dynamicNodeInstance, helper);
96  0 if ((orgIds != null) && (orgIds.size() > 1)) {
97  0 throw new RuntimeException("Found a total of " + orgIds.size() + " organizations for routing on document when only one is allowed.");
98    }
99  0 DynamicResult result = new DynamicResult(orgIds == null || orgIds.isEmpty(), null);
100  0 for (String orgId : orgIds) {
101  0 RouteNodeInstance nodeInstance = generateNextNodeInstance(orgId, roleNodePrototype, context, dynamicNodeInstance.getBranch(), helper);
102  0 LOG.debug("Exiting transitioningInto with " + ((nodeInstance == null) ? "no" : "a") + " valid next node instance");
103  0 if (nodeInstance != null) {
104  0 result.setNextNodeInstance(nodeInstance);
105    }
106    }
107  0 return result;
108    }
109   
110    /**
111    * This method is used by the {@link #transitioningInto(RouteContext, RouteNodeInstance, RouteHelper)} method and
112    * the organization id values returned will be used to generate node instances that will begin the dynamic
113    * organization routing.
114    *
115    * @param context
116    * - RouteContext class that holds data about the current document's routing and data
117    * @param dynamicNodeInstance
118    * - The initial instance of the dynamic node as determined by the route node configuration
119    * @param helper
120    * - RouteHelper convenience class used to make some routing operations a bit easier
121    * @return A list of organization ids that will be used to create next node instances in the routing of the
122    * document. By default these are the organizations set in the the current document's Document Content xml
123    * by Kuali Student at the point of Save and/or Submit
124    */
 
125  0 toggle protected List<String> getInitialOrganizationIdsForRouting(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteHelper helper) {
126  0 List<String> orgIds = getOrganizationIdsFromDocumentContent(context);
127  0 if ((orgIds != null) && (orgIds.size() > 1)) {
128  0 throw new RuntimeException("Found a total of " + orgIds.size() + " organizations for routing on document when only one is allowed.");
129    }
130   
131  0 try {
132  0 for (String orgId : orgIds) {
133  0 OrgInfo orgInfo = getOrganizationService().getOrganization(orgId);
134  0 LOG.debug("Org on Document: " + getOrgInfoForPrint(orgInfo));
135  0 List<OrgOrgRelationInfo> orgRelationInfos = getOrganizationService().getOrgOrgRelationsByOrg(orgId);
136  0 for (OrgOrgRelationInfo orgOrgRelationInfo : orgRelationInfos) {
137  0 LOG.debug("---- Org Relation:");
138  0 LOG.debug("------------ Org ID: " + orgOrgRelationInfo.getOrgId());
139  0 orgInfo = getOrganizationService().getOrganization(orgOrgRelationInfo.getRelatedOrgId());
140  0 LOG.debug("------------ Related Org on Document: " + getOrgInfoForPrint(orgInfo));
141  0 LOG.debug("------------ Relation State: " + orgOrgRelationInfo.getState());
142  0 LOG.debug("------------ Relation Type: " + orgOrgRelationInfo.getType());
143    }
144  0 List<OrgOrgRelationInfo> relatedOrgRelationInfos = getOrganizationService().getOrgOrgRelationsByRelatedOrg(orgId);
145  0 for (OrgOrgRelationInfo orgOrgRelationInfo : relatedOrgRelationInfos) {
146  0 LOG.debug("---- Related Org Relation:");
147  0 LOG.debug("------------ Related Org ID: " + orgOrgRelationInfo.getRelatedOrgId());
148  0 orgInfo = getOrganizationService().getOrganization(orgOrgRelationInfo.getOrgId());
149  0 LOG.debug("------------ Org of Relation: " + getOrgInfoForPrint(orgInfo));
150  0 LOG.debug("------------ Relation State: " + orgOrgRelationInfo.getState());
151  0 LOG.debug("------------ Relation Type: " + orgOrgRelationInfo.getType());
152    }
153    }
154    } catch (Exception e) {
155  0 LOG.error(e);
156  0 throw new RuntimeException("Caught Exception using Organization Service", e);
157    }
158  0 return orgIds;
159    }
160   
161    /**
162    * Method to fetch the organization ids from the KEW document content xml
163    *
164    * @param context
165    * - RouteContext class that holds data about the current document's routing and data
166    * @return A list of organization ids that are listed in the xml (may have duplicates if duplicates are allowed by
167    * KS code)
168    */
 
169  0 toggle protected List<String> getOrganizationIdsFromDocumentContent(RouteContext context) {
170  0 Document xmlContent = context.getDocumentContent().getDocument();
171  0 XPath xPath = XPathHelper.newXPath();
172  0 try {
173  0 List<String> orgIds = new ArrayList<String>();
174  0 NodeList orgElements = (NodeList) xPath.evaluate("/documentContent/applicationContent/" + AbstractCocOrgQualifierResolver.DOCUMENT_CONTENT_XML_ROOT_ELEMENT_NAME + "/orgId", xmlContent,
175    XPathConstants.NODESET);
176  0 for (int index = 0; index < orgElements.getLength(); index++) {
177  0 Element attributeElement = (Element) orgElements.item(index);
178  0 String attributeValue = attributeElement.getTextContent();
179  0 orgIds.add(attributeValue);
180    }
181  0 if (LOG.isDebugEnabled()) {
182  0 LOG.debug("Found " + orgElements.getLength() + " organization ids to parse for routing:");
183  0 XmlHelper.printDocumentStructure(xmlContent);
184    }
185  0 return orgIds;
186    } catch (XPathExpressionException e) {
187  0 throw new RiceRuntimeException("Encountered an issue executing XPath.", e);
188    }
189    }
190   
 
191  0 toggle @Override
192    public DynamicResult transitioningOutOf(RouteContext context, RouteHelper helper) throws Exception {
193  0 LOG.debug("Variables for transitioningOutOf");
194  0 RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
195   
196  0 List<String> relatedOrgIds = getNextOrganizationIdsForRouting(context, helper);
197    // dynamic routing is complete if there are no more related org ids
198  0 DynamicResult result = new DynamicResult(relatedOrgIds.isEmpty(), null);
199  0 for (String relatedOrgId : relatedOrgIds) {
200  0 RouteNodeInstance nodeInstance = generateNextNodeInstance(relatedOrgId, context, processInstance.getBranch(), helper);
201  0 result.getNextNodeInstances().add(nodeInstance);
202    }
203  0 return result;
204    }
205   
206    /**
207    * Convenience method to get a consistent organization data in order to print to the log
208    */
 
209  0 toggle protected String getOrgInfoForPrint(OrgInfo orgInfo) {
210  0 return orgInfo.getId() + " - " + orgInfo.getShortName() + " (" + orgInfo.getLongName() + ")";
211    }
212   
213    /**
214    * This method is used by the {@link #transitioningOutOf(RouteContext, RouteHelper)} method and the organization id
215    * values returned will be used to generate node instances that will continue the dynamic organization routing.
216    *
217    * The default implementation retrieves the organization from the previous route node and uses the
218    * {@link OrganizationService#getOrgOrgRelationsByRelatedOrg(String)} method to find all organization relations for
219    * it. That list is then parsed to find all organization relations that are both active and of the relation type
220    * that matches {@link AbstractCocOrgQualifierResolver#KUALI_ORG_TYPE_CURRICULUM_PARENT}. A unique list of those
221    * organization ids is returned.
222    *
223    * @param context
224    * - RouteContext class that holds data about the current document's routing and data
225    * @param helper
226    * - RouteHelper convenience class used to make some routing operations a bit easier
227    * @return A list of organization ids that will be used to create next node instances in the routing of the
228    * document.
229    */
 
230  0 toggle protected List<String> getNextOrganizationIdsForRouting(RouteContext context, RouteHelper helper) {
231  0 RouteNodeInstance currentNode = context.getNodeInstance();
232  0 String currentNodeName = currentNode.getName();
233  0 LOG.debug("currentNodeName = '" + currentNodeName + "'");
234  0 NodeState currentNodeOrgIdState = currentNode.getNodeState(NODE_STATE_ORG_ID_KEY);
235  0 LOG.debug("currentNodeOrgIdState is " + ((currentNodeOrgIdState != null) ? "not " : "") + "null");
236  0 String currentNodeOrgId = (currentNodeOrgIdState != null) ? currentNodeOrgIdState.getValue() : null;
237  0 LOG.debug("currentNodeOrgId = '" + currentNodeOrgId + "'");
238  0 Set<String> relatedOrgIds = new HashSet<String>();
239  0 try {
240  0 List<OrgOrgRelationInfo> relatedOrgRelationInfos = getOrganizationService().getOrgOrgRelationsByRelatedOrg(currentNodeOrgId);
241  0 for (OrgOrgRelationInfo orgOrgRelationInfo : relatedOrgRelationInfos) {
242  0 if (StringUtils.equals("Active", orgOrgRelationInfo.getState())) {
243  0 if (StringUtils.equals(AbstractCocOrgQualifierResolver.KUALI_ORG_TYPE_CURRICULUM_PARENT, orgOrgRelationInfo.getType())) {
244  0 LOG.debug("---- Related Org Relation:");
245  0 OrgInfo referenceOrgInfo = getOrganizationService().getOrganization(orgOrgRelationInfo.getRelatedOrgId());
246  0 OrgInfo nextNodeOrgInfo = getOrganizationService().getOrganization(orgOrgRelationInfo.getOrgId());
247  0 LOG.debug("------------ Reference Org: " + getOrgInfoForPrint(referenceOrgInfo));
248  0 LOG.debug("------------ Org for Next Node: " + getOrgInfoForPrint(nextNodeOrgInfo));
249  0 relatedOrgIds.add(nextNodeOrgInfo.getId());
250    }
251    }
252    }
253    } catch (Exception e) {
254  0 LOG.error("Exception caught attempting to use org hierarchy routing", e);
255  0 throw new RuntimeException("Exception caught attempting to use org hierarchy routing", e);
256    }
257  0 return new ArrayList<String>(relatedOrgIds);
258    }
259   
260    /**
261    * Generates a new node instance for the given organization id using the default prototype 'role' route node
262    * definition created by the {@link #setUpDocumentType(DocumentType, RouteNodeInstance)} method.
263    *
264    */
 
265  0 toggle protected RouteNodeInstance generateNextNodeInstance(String orgId, RouteContext context, Branch branch, RouteHelper helper) {
266  0 return generateNextNodeInstance(orgId, helper.getNodeFactory().getRouteNode(context, ORG_HIERARCHY_NODE), context, branch, helper);
267    }
268   
269    /**
270    * Generates a new node instance for the given organization id using the given route node definition.
271    *
272    */
 
273  0 toggle protected RouteNodeInstance generateNextNodeInstance(String orgId, RouteNode routeNodeDefinition, RouteContext context, Branch branch, RouteHelper helper) {
274  0 LOG.debug("Adding new node with name '" + routeNodeDefinition.getRouteNodeName() + "'");
275  0 RouteNodeInstance actualRouteNodeInstance = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getRouteHeaderId(), routeNodeDefinition);
276  0 actualRouteNodeInstance.setBranch(branch);
277  0 actualRouteNodeInstance.addNodeState(new NodeState(NODE_STATE_ORG_ID_KEY, orgId));
278  0 return actualRouteNodeInstance;
279    }
280   
281    /**
282    * Method verifies that the Organization Hierarchy Review node exists on the document type. If it does not exist it
283    * will add it and save the document type. This node is required because it will be used as a prototype for any
284    * generated 'role' nodes (also known as KIM Responsibility Review Nodes).
285    *
286    * @param documentType
287    * - DocumentType object that needs nodes defined but may not have them defined
288    * @param dynamicNodeInstance
289    * - The node instance that represents the dynamic node as defined in the document type configuration
290    * (the node that tells KEW to look at this class for the node processing)
291    */
 
292  0 toggle protected DocumentType setUpDocumentType(DocumentType documentType, RouteNodeInstance dynamicNodeInstance) {
293  0 boolean altered = false;
294    // add the org hierarchy review node
295  0 if (documentType.getNamedProcess(ORG_HIERARCHY_NODE) == null) {
296  0 RouteNode hierarchyNode = getKimRoleNode(ORG_HIERARCHY_NODE, dynamicNodeInstance);
297  0 documentType.addProcess(getPrototypeProcess(hierarchyNode, documentType));
298  0 altered = true;
299    }
300  0 if (altered) {
301    // side step normal version etc. because it can cause exceptions
302  0 KEWServiceLocator.getDocumentTypeService().save(documentType);
303    }
304  0 return KEWServiceLocator.getDocumentTypeService().findByName(documentType.getName());
305    }
306   
307    /**
308    * Method generates the {@link RouteNode} definition that will be used as a prototype for any dynamically created route node instances for this dynamic node class.
309    *
310    * @param routeNodeName - The name to be used for the new route node definition
311    * @param dynamicNodeInstance - used to set up the {@link DocumentType} on the generated route node definition
312    */
 
313  0 toggle protected RouteNode getKimRoleNode(String routeNodeName, RouteNodeInstance dynamicNodeInstance) {
314  0 RouteNode roleNode = new RouteNode();
315  0 roleNode.setFinalApprovalInd(Boolean.FALSE);
316  0 roleNode.setMandatoryRouteInd(Boolean.FALSE);
317  0 roleNode.setActivationType(KEWConstants.ROUTE_LEVEL_PARALLEL);
318  0 roleNode.setDocumentType(dynamicNodeInstance.getRouteNode().getDocumentType());
319  0 roleNode.setNodeType(RoleNode.class.getName());
320  0 roleNode.setRouteMethodName(RoleRouteModule.class.getName());
321  0 roleNode.setRouteMethodCode(KEWConstants.ROUTE_LEVEL_ROUTE_MODULE);
322  0 roleNode.setRouteNodeName(routeNodeName);
323  0 roleNode.setContentFragment("<" + QUALIFIER_RESOLVER_CLASS_ELEMENT + ">" + OrganizationCurriculumCommitteeQualifierResolver.class.getName() + "</" + QUALIFIER_RESOLVER_CLASS_ELEMENT + ">");
324  0 return roleNode;
325    }
326   
 
327  0 toggle protected Process getPrototypeProcess(RouteNode node, DocumentType documentType) {
328  0 Process process = new Process();
329  0 process.setDocumentType(documentType);
330  0 process.setInitial(false);
331  0 process.setInitialRouteNode(node);
332  0 process.setName(node.getRouteNodeName());
333  0 return process;
334    }
335   
336    }