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