001 /** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.kew.rule.web; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.log4j.Logger; 020 import org.apache.struts.action.ActionForm; 021 import org.apache.struts.action.ActionForward; 022 import org.apache.struts.action.ActionMapping; 023 import org.kuali.rice.core.api.criteria.Predicate; 024 import org.kuali.rice.core.api.criteria.QueryByCriteria; 025 import org.kuali.rice.kew.doctype.bo.DocumentType; 026 import org.kuali.rice.kew.doctype.service.DocumentTypeService; 027 import org.kuali.rice.kew.engine.node.ProcessDefinitionBo; 028 import org.kuali.rice.kew.engine.node.RouteNode; 029 import org.kuali.rice.kew.engine.node.service.RouteNodeService; 030 import org.kuali.rice.kew.service.KEWServiceLocator; 031 import org.kuali.rice.kew.api.KewApiConstants; 032 import org.kuali.rice.kew.web.KewKualiAction; 033 import org.kuali.rice.kim.api.KimConstants; 034 import org.kuali.rice.kim.api.permission.Permission; 035 import org.kuali.rice.kim.api.permission.PermissionService; 036 import org.kuali.rice.kim.api.responsibility.Responsibility; 037 import org.kuali.rice.kim.api.responsibility.ResponsibilityService; 038 import org.kuali.rice.kim.api.role.Role; 039 import org.kuali.rice.kim.api.role.RoleService; 040 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 041 import org.kuali.rice.kim.bo.impl.KimAttributes; 042 import org.kuali.rice.kim.impl.permission.GenericPermissionBo; 043 import org.kuali.rice.kim.impl.permission.PermissionBo; 044 import org.kuali.rice.kim.impl.permission.PermissionTemplateBo; 045 import org.kuali.rice.kim.impl.responsibility.ReviewResponsibilityBo; 046 import org.kuali.rice.kns.service.DocumentHelperService; 047 import org.kuali.rice.kns.service.KNSServiceLocator; 048 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 049 import org.kuali.rice.krad.service.DataDictionaryService; 050 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 051 import org.kuali.rice.krad.util.GlobalVariables; 052 import org.kuali.rice.krad.util.KRADConstants; 053 054 import javax.servlet.http.HttpServletRequest; 055 import javax.servlet.http.HttpServletResponse; 056 import java.util.ArrayList; 057 import java.util.Collections; 058 import java.util.HashMap; 059 import java.util.HashSet; 060 import java.util.LinkedHashMap; 061 import java.util.List; 062 import java.util.Map; 063 import java.util.Set; 064 065 import static org.kuali.rice.core.api.criteria.PredicateFactory.*; 066 067 068 /** 069 * This is a description of what this class does - kellerj don't forget to fill this in. 070 * 071 * @author Kuali Rice Team (rice.collab@kuali.org) 072 * 073 */ 074 public class DocumentConfigurationViewAction extends KewKualiAction { 075 076 private static final Logger LOG = Logger.getLogger(DocumentConfigurationViewAction.class); 077 078 private PermissionService permissionService; 079 private RoleService roleService; 080 private ResponsibilityService responsibilityService; 081 private DocumentTypeService documentTypeService; 082 private DataDictionaryService dataDictionaryService; 083 private RouteNodeService routeNodeService; 084 private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; 085 private DocumentHelperService documentHelperService; 086 087 public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 088 populateForm( (DocumentConfigurationViewForm)form ); 089 return mapping.findForward("basic"); 090 } 091 092 protected void populateForm( DocumentConfigurationViewForm form ) { 093 if ( StringUtils.isNotEmpty( form.getDocumentTypeName() ) ) { 094 form.setDocumentType( getDocumentTypeService().findByName( form.getDocumentTypeName() ) ); 095 if ( form.getDocumentType() != null ) { 096 form.getDocumentType().getChildrenDocTypes(); 097 form.setAttributeLabels( new HashMap<String, String>() ); 098 populateRelatedDocuments( form ); 099 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 }