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