View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.rule.web;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.apache.struts.action.ActionForm;
21  import org.apache.struts.action.ActionForward;
22  import org.apache.struts.action.ActionMapping;
23  import org.kuali.rice.core.api.criteria.Predicate;
24  import org.kuali.rice.core.api.criteria.QueryByCriteria;
25  import org.kuali.rice.kew.doctype.bo.DocumentType;
26  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
27  import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
28  import org.kuali.rice.kew.engine.node.RouteNode;
29  import org.kuali.rice.kew.engine.node.service.RouteNodeService;
30  import org.kuali.rice.kew.service.KEWServiceLocator;
31  import org.kuali.rice.kew.api.KewApiConstants;
32  import org.kuali.rice.kew.web.KewKualiAction;
33  import org.kuali.rice.kim.api.KimConstants;
34  import org.kuali.rice.kim.api.permission.Permission;
35  import org.kuali.rice.kim.api.permission.PermissionService;
36  import org.kuali.rice.kim.api.responsibility.Responsibility;
37  import org.kuali.rice.kim.api.responsibility.ResponsibilityService;
38  import org.kuali.rice.kim.api.role.Role;
39  import org.kuali.rice.kim.api.role.RoleService;
40  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
41  import org.kuali.rice.kim.bo.impl.KimAttributes;
42  import org.kuali.rice.kim.impl.permission.PermissionBo;
43  import org.kuali.rice.kim.impl.permission.PermissionTemplateBo;
44  import org.kuali.rice.kim.impl.responsibility.ResponsibilityBo;
45  import org.kuali.rice.kns.service.DocumentHelperService;
46  import org.kuali.rice.kns.service.KNSServiceLocator;
47  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
48  import org.kuali.rice.krad.service.DataDictionaryService;
49  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
50  import org.kuali.rice.krad.util.GlobalVariables;
51  import org.kuali.rice.krad.util.KRADConstants;
52  
53  import javax.servlet.http.HttpServletRequest;
54  import javax.servlet.http.HttpServletResponse;
55  import java.util.ArrayList;
56  import java.util.Collections;
57  import java.util.HashMap;
58  import java.util.HashSet;
59  import java.util.LinkedHashMap;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Set;
63  
64  import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
65  
66  
67  /**
68   * This is a description of what this class does - kellerj don't forget to fill this in. 
69   * 
70   * @author Kuali Rice Team (rice.collab@kuali.org)
71   *
72   */
73  public class DocumentConfigurationViewAction extends KewKualiAction {
74  
75  	private static final Logger LOG = Logger.getLogger(DocumentConfigurationViewAction.class);
76  	
77  	private PermissionService permissionService;
78  	private RoleService roleService;
79  	private ResponsibilityService responsibilityService;
80  	private DocumentTypeService documentTypeService;
81  	private DataDictionaryService dataDictionaryService;
82  	private RouteNodeService routeNodeService;
83  	private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
84  	private DocumentHelperService documentHelperService;
85  	
86      public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
87      	populateForm( (DocumentConfigurationViewForm)form );
88          return mapping.findForward("basic");
89      }
90      
91      protected void populateForm( DocumentConfigurationViewForm form ) {
92      	if ( StringUtils.isNotEmpty( form.getDocumentTypeName() ) ) {
93      		form.setDocumentType( getDocumentTypeService().findByName( form.getDocumentTypeName() ) ); 
94          	if ( form.getDocumentType() != null ) {
95  	    		form.getDocumentType().getChildrenDocTypes();
96  	    		form.setAttributeLabels( new HashMap<String, String>() );
97  	    		populateRelatedDocuments( form );
98  	    		populatePermissions( form );
99  	    		populateRoutingResponsibilities( form );
100 	    		populateRoutingExceptionResponsibility( form );
101 	    		checkPermissions( form );
102         	}
103     	}
104     }
105 
106     protected void checkPermissions( DocumentConfigurationViewForm form ) {
107     	String docTypeDocumentType = getMaintenanceDocumentDictionaryService().getDocumentTypeName(DocumentType.class);
108         try {
109             if ((docTypeDocumentType != null) && getDocumentHelperService().getDocumentAuthorizer(docTypeDocumentType).canInitiate(docTypeDocumentType, GlobalVariables.getUserSession().getPerson())) {
110                 form.setCanInitiateDocumentTypeDocument( true );
111             }
112         } catch (Exception ex) {
113 			// just skip - and don't display links
114         	LOG.error( "Unable to check DocumentType initiation permission for "+ docTypeDocumentType, ex );
115 		}
116     	String permissionDocumentType = getMaintenanceDocumentDictionaryService().getDocumentTypeName(PermissionBo.class);
117         try {
118             if ((permissionDocumentType != null) && getDocumentHelperService().getDocumentAuthorizer(permissionDocumentType).canInitiate(permissionDocumentType, GlobalVariables.getUserSession().getPerson())) {
119                 form.setCanInitiatePermissionDocument( true );
120             }
121         } catch (Exception ex) {
122 			// just skip - and don't display links
123         	LOG.error( "Unable to check Permission initiation permission for "+ permissionDocumentType, ex );
124 		}
125     	String responsibilityDocumentType = getMaintenanceDocumentDictionaryService().getDocumentTypeName(ResponsibilityBo.class);
126         try {
127             if ((responsibilityDocumentType != null) && getDocumentHelperService().getDocumentAuthorizer(responsibilityDocumentType).canInitiate(responsibilityDocumentType, GlobalVariables.getUserSession().getPerson())) {
128                 form.setCanInitiateResponsibilityDocument( true );
129             }
130         } catch (Exception ex) {
131 			// just skip - and don't display links
132         	LOG.error( "Unable to check Responsibility initiation permission for "+ responsibilityDocumentType, ex );
133 		}
134     }
135     
136     @SuppressWarnings("unchecked")
137 	public void populateRelatedDocuments( DocumentConfigurationViewForm form ) {
138     	form.setParentDocumentType( form.getDocumentType().getParentDocType() );
139     	form.setChildDocumentTypes( new ArrayList<DocumentType>( form.getDocumentType().getChildrenDocTypes() ) );
140     }
141     
142 	public void populatePermissions( DocumentConfigurationViewForm form ) {
143 		
144 		DocumentType docType = form.getDocumentType();
145 		Map<String,List<Role>> permRoles = new HashMap<String, List<Role>>();
146 		// loop over the document hierarchy
147 		Set<String> seenDocumentPermissions = new HashSet<String>();
148 		while ( docType != null) {
149 			String documentTypeName = docType.getName();
150             Predicate p = and(
151                 equal("active", "Y"),
152                 equal("attributes[" + KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME + "]", docType.getName()));
153 			List<Permission> perms = getPermissionService().findPermissions(QueryByCriteria.Builder.fromPredicates(p)).getResults();
154 			for ( Permission perm : perms ) {
155                 PermissionBo permBo = PermissionBo.from(perm);
156 				List<String> roleIds = getPermissionService().getRoleIdsForPermission(perm.getNamespaceCode(),
157                         perm.getName());
158                 if (!roleIds.isEmpty()) {
159 				    permRoles.put( perm.getId(), getRoleService().getRoles(roleIds) );
160                 }
161 				for ( String attributeName : permBo.getDetails().keySet() ) {
162 					addAttributeLabel(form, attributeName);
163 				}
164 			}
165 			// show the section if the current document or permissions exist
166 			if ( perms.size() > 0 || documentTypeName.equals( form.getDocumentTypeName() ) ) {
167 				ArrayList<PermissionForDisplay> dispPerms = new ArrayList<PermissionForDisplay>( perms.size() );
168 				for ( Permission perm : perms ) {
169                     PermissionBo permBo = PermissionBo.from(perm);
170 					if ( permBo.getDetails().size() == 1  ) { // only the document type
171 						// this is a document type-specific permission, check if seen earlier
172 						if ( seenDocumentPermissions.contains(perm.getTemplate().getNamespaceCode()+"|"+perm.getTemplate().getName()) ) {
173 							dispPerms.add( new PermissionForDisplay( permBo, true ) );
174 						} else {
175 							dispPerms.add( new PermissionForDisplay( permBo, false ) );
176 							seenDocumentPermissions.add(perm.getTemplate().getNamespaceCode()+"|"+perm.getTemplate().getName());
177 						}
178 					} else {
179 						// other attributes, can't determine whether this is overridden at another level
180 						dispPerms.add( new PermissionForDisplay( permBo, false ) );
181 					}
182 				}
183 				form.setPermissionsForDocumentType(documentTypeName, dispPerms );
184 				form.addDocumentType(documentTypeName);
185 			}
186 			docType = docType.getParentDocType();			
187 		}
188 		
189 		form.setPermissionRoles( permRoles );
190 	}
191 	
192 	protected void populateRoutingExceptionResponsibility( DocumentConfigurationViewForm form ) {	
193 		DocumentType docType = form.getDocumentType();
194 		List<ResponsibilityForDisplay> responsibilities = new ArrayList<ResponsibilityForDisplay>();
195 		while ( docType != null) {
196             QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
197             Predicate p = and(
198                 equal("template.namespaceCode", KRADConstants.KUALI_RICE_WORKFLOW_NAMESPACE),
199                 equal("template.name", KewApiConstants.EXCEPTION_ROUTING_RESPONSIBILITY_TEMPLATE_NAME),
200                 equal("active", "Y"),
201                 equal("attributes[documentTypeName]", docType.getName())
202             );
203             builder.setPredicates(p);
204 			List<Responsibility> resps = getResponsibilityService().findResponsibilities(builder.build()).getResults();
205 			
206 			for ( Responsibility r : resps ) {
207 				if ( responsibilities.isEmpty() ) {
208 					responsibilities.add( new ResponsibilityForDisplay( r, false ) );
209 				} else {
210 					responsibilities.add( new ResponsibilityForDisplay( r, true ) );
211 				}
212 			}
213 			docType = docType.getParentDocType();			
214 		}
215 		form.setExceptionResponsibilities( responsibilities );
216 		for ( ResponsibilityForDisplay responsibility : responsibilities ) {
217 			List<String> roleIds = getResponsibilityService().getRoleIdsForResponsibility(responsibility.getResp().getId());
218             if (!roleIds.isEmpty()) {
219 			    form.getResponsibilityRoles().put( responsibility.getResponsibilityId(), getRoleService().getRoles(roleIds) );
220             }
221 		}
222 	}
223 
224 	protected void addAttributeLabel( DocumentConfigurationViewForm form, String attributeName ) {
225 		if ( !form.getAttributeLabels().containsKey(attributeName) ) {
226 			form.getAttributeLabels().put(attributeName, 
227 					getDataDictionaryService().getAttributeLabel(KimAttributes.class, attributeName) );
228 		}
229 	}
230 	
231 	// loop over nodes
232 	// if split node, push onto stack
233 		// note the number of children, this is the number of times the join node needs to be found
234 	// when join node found, return to last split on stack
235 		// move to next child of the split
236 	
237 	protected RouteNode flattenSplitNode( RouteNode splitNode, Map<String,RouteNode> nodes ) {
238 		nodes.put( splitNode.getRouteNodeName(), splitNode );
239 		RouteNode joinNode = null;
240 		
241 		for ( RouteNode nextNode : splitNode.getNextNodes() ) {
242 			joinNode = flattenRouteNodes(nextNode, nodes);
243 		}
244 		
245 		if ( joinNode != null ) {
246 			nodes.put( joinNode.getRouteNodeName(), joinNode );
247 		}
248 		return joinNode;
249 	}
250 	
251 	/**
252 	 * @param node
253 	 * @param nodes
254 	 * @return The last node processed by this method.
255 	 */
256 	protected RouteNode flattenRouteNodes( RouteNode node, Map<String,RouteNode> nodes ) {
257 		RouteNode lastProcessedNode = null;
258         if (node != null) {
259             // if we've seen the node before - skip, avoids infinite loop
260             if ( nodes.containsKey(node.getRouteNodeName()) ) {
261                 return node;
262             }
263 		
264             if ( node.getNodeType().contains( "SplitNode" ) ) { // Hacky - but only way when the class may not be present in the KEW JVM
265                 lastProcessedNode = flattenSplitNode(node, nodes); // special handling to process all split children before continuing
266                 // now, process the join node's children
267                 if (lastProcessedNode != null) {
268                     for ( RouteNode nextNode : lastProcessedNode.getNextNodes() ) {
269                         lastProcessedNode = flattenRouteNodes(nextNode, nodes);
270                     }
271                 }
272             } else if ( node.getNodeType().contains( "JoinNode" ) ) {
273                 lastProcessedNode = node; // skip, handled by the split node
274             } else {
275                 // normal node, add to list and process all children
276                 nodes.put(node.getRouteNodeName(), node);
277                 for ( RouteNode nextNode : node.getNextNodes() ) {
278                     lastProcessedNode = flattenRouteNodes(nextNode, nodes);
279                 }
280             }
281         }
282 		return lastProcessedNode;
283 	}
284 	
285 	@SuppressWarnings("unchecked")
286 	public void populateRoutingResponsibilities( DocumentConfigurationViewForm form ) {
287 		// pull all the responsibilities
288 		// merge the data and attach to route levels
289 		// pull the route levels and store on form
290 		//List<RouteNode> routeNodes = getRouteNodeService().getFlattenedNodes(form.getDocumentType(), true);
291         Map<String,List<Role>> respToRoleMap = new HashMap<String, List<Role>>();
292         List<ProcessDefinitionBo> processes = (List<ProcessDefinitionBo>)form.getDocumentType().getProcesses();
293         if (!(processes.isEmpty())) {
294             RouteNode rootNode = processes.get(0).getInitialRouteNode();
295             LinkedHashMap<String, RouteNode> routeNodeMap = new LinkedHashMap<String, RouteNode>();
296             flattenRouteNodes(rootNode, routeNodeMap);
297 		
298             form.setRouteNodes( new ArrayList<RouteNode>( routeNodeMap.values() ) );
299             // pull all the responsibilities and store into a map for use by the JSP
300 		
301             // FILTER TO THE "Review" template only
302             // pull responsibility roles
303             DocumentType docType = form.getDocumentType();
304             Set<Responsibility> responsibilities = new HashSet<Responsibility>();
305             Map<String,List<ResponsibilityForDisplay>> nodeToRespMap = new LinkedHashMap<String, List<ResponsibilityForDisplay>>();
306             while ( docType != null) {
307                 QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
308                 Predicate p = and(
309                         equal("template.namespaceCode", KRADConstants.KUALI_RICE_WORKFLOW_NAMESPACE),
310                         equal("template.name", KewApiConstants.DEFAULT_RESPONSIBILITY_TEMPLATE_NAME),
311                         equal("active", "Y"),
312                         equal("attributes[documentTypeName]", docType.getName())
313                 );
314                 builder.setPredicates(p);
315                 List<Responsibility> resps = getResponsibilityService().findResponsibilities(builder.build()).getResults();
316 			
317                 for ( Responsibility r : resps ) {
318                     String routeNodeName = r.getAttributes().get(KimConstants.AttributeConstants.ROUTE_NODE_NAME);
319                     if ( StringUtils.isNotBlank(routeNodeName) ) {
320                         if ( !nodeToRespMap.containsKey( routeNodeName ) ) {
321                             nodeToRespMap.put(routeNodeName, new ArrayList<ResponsibilityForDisplay>() );
322                             nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, false ) );
323                         } else {
324                             // check if the responsibility in the existing list is for the current document
325                             // if so, OK to add.  Otherwise, a lower level document has overridden this
326                             // responsibility (since we are walking up the hierarchy
327                             if ( nodeToRespMap.get(routeNodeName).get(0).getDetails().get( KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME ).equals(docType.getName() ) ) {
328                                 nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, false ) );
329                             } else { // doc type name did not match, mark as overridden
330                                 nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, true ) );
331                             }
332                         }
333                         responsibilities.add(r);
334                     }
335                 }
336                 docType = docType.getParentDocType();			
337             }
338             form.setResponsibilityMap( nodeToRespMap );
339 		
340 		    for (Responsibility responsibility : responsibilities ) {
341 		        List<String> roleIds = getResponsibilityService().getRoleIdsForResponsibility(responsibility.getId());
342                 if (!roleIds.isEmpty()) {
343 		            respToRoleMap.put( responsibility.getId(), getRoleService().getRoles(roleIds) );
344                 }
345 		    }
346         }
347 		form.setResponsibilityRoles( respToRoleMap );
348 	}
349 
350 	@Override
351 	public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
352 		// Repopulating the form is necessary when toggling tab states on the server side.
353 		ActionForward actionForward = super.toggleTab(mapping, form, request, response);
354 		populateForm( (DocumentConfigurationViewForm)form );
355 		return actionForward;
356 	}
357 
358 	/**
359 	 * Internal delegate class to wrap a responsibility and add an overridden flag.
360 	 */
361 	public static class ResponsibilityForDisplay {
362 
363 		private Responsibility resp;
364 		private boolean overridden = false;
365 		
366 		public ResponsibilityForDisplay( Responsibility resp, boolean overridden ) {
367 			this.resp = resp;
368 			this.overridden = overridden;
369 		}
370 		
371 		/**
372 		 * @return the resp
373 		 */
374 		Responsibility getResp() {
375 			return this.resp;
376 		}
377 		
378 		public boolean isOverridden() {
379 			return this.overridden;
380 		}
381 
382 		public void setOverridden(boolean overridden) {
383 			this.overridden = overridden;
384 		}
385 
386 		public Map<String, String> getDetails() {
387 			return new HashMap<String, String>(this.resp.getAttributes());
388 		}
389 
390 		public String getName() {
391 			return this.resp.getName();
392 		}
393 
394 		public String getNamespaceCode() {
395 			return this.resp.getNamespaceCode();
396 		}
397 
398 		public String getResponsibilityId() {
399 			return this.resp.getId();
400 		}
401 	}
402 
403 	public static class PermissionForDisplay {
404 		private PermissionBo perm;
405 		private boolean overridden = false;
406 		
407 		public PermissionForDisplay( PermissionBo perm, boolean overridden ) {
408 			this.perm = perm;
409 			this.overridden = overridden;
410 		}
411 		public boolean isOverridden() {
412 			return this.overridden;
413 		}
414 
415 		public void setOverridden(boolean overridden) {
416 			this.overridden = overridden;
417 		}
418 		public Map<String, String> getDetails() {
419 			return this.perm.getDetails();
420 		}
421 		public String getName() {
422 			return this.perm.getName();
423 		}
424 		public String getNamespaceCode() {
425 			return this.perm.getNamespaceCode();
426 		}
427         public String getId() {
428             return this.perm.getId();
429         }
430 		public PermissionTemplateBo getTemplate() {
431 			return this.perm.getTemplate();
432 		}
433 		
434 	}
435 	
436 	/**
437 	 * @return the permissionService
438 	 */
439 	public PermissionService getPermissionService() {
440 		if ( permissionService == null ) {
441 			permissionService = KimApiServiceLocator.getPermissionService();
442 		}
443 		return permissionService;
444 	}
445 
446 	/**
447 	 * @return the roleService
448 	 */
449 	public RoleService getRoleService() {
450 		if ( roleService == null ) {
451 			roleService = KimApiServiceLocator.getRoleService();
452 		}
453 		return roleService;
454 	}
455 
456 	/**
457 	 * @return the responsibilityService
458 	 */
459 	public ResponsibilityService getResponsibilityService() {
460 		if ( responsibilityService == null ) {
461 			responsibilityService = KimApiServiceLocator.getResponsibilityService();
462 		}
463 		return responsibilityService;
464 	}
465 
466 	/**
467 	 * @return the documentTypeService
468 	 */
469 	public DocumentTypeService getDocumentTypeService() {
470 		if ( documentTypeService == null ) {
471 			documentTypeService = KEWServiceLocator.getDocumentTypeService();
472 		}
473 		return documentTypeService;
474 	}
475 
476 	public DataDictionaryService getDataDictionaryService() {
477 		if(dataDictionaryService == null){
478 			dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
479 		}
480 		return dataDictionaryService;
481 	}
482 
483 	public RouteNodeService getRouteNodeService() {
484 		if ( routeNodeService == null ) {
485 			routeNodeService = KEWServiceLocator.getRouteNodeService();
486 		}
487 		return routeNodeService;
488 	}
489 
490 	public DocumentHelperService getDocumentHelperService() {
491 		if(documentHelperService == null){
492 			documentHelperService = KNSServiceLocator.getDocumentHelperService();
493 		}
494 		return documentHelperService;
495 	}
496 
497 	public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
498 		if(maintenanceDocumentDictionaryService == null){
499 			maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
500 		}
501 		return maintenanceDocumentDictionaryService;
502 	}
503 	
504 }