001 /** 002 * Copyright 2005-2013 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.PermissionBo; 043 import org.kuali.rice.kim.impl.permission.PermissionTemplateBo; 044 import org.kuali.rice.kim.impl.responsibility.ResponsibilityBo; 045 import org.kuali.rice.kns.service.DocumentHelperService; 046 import org.kuali.rice.kns.service.KNSServiceLocator; 047 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 048 import org.kuali.rice.krad.service.DataDictionaryService; 049 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 050 import org.kuali.rice.krad.util.GlobalVariables; 051 import org.kuali.rice.krad.util.KRADConstants; 052 053 import javax.servlet.http.HttpServletRequest; 054 import javax.servlet.http.HttpServletResponse; 055 import java.util.ArrayList; 056 import java.util.Collections; 057 import java.util.HashMap; 058 import java.util.HashSet; 059 import java.util.LinkedHashMap; 060 import java.util.List; 061 import java.util.Map; 062 import java.util.Set; 063 064 import static org.kuali.rice.core.api.criteria.PredicateFactory.*; 065 066 067 /** 068 * This is a description of what this class does - kellerj don't forget to fill this in. 069 * 070 * @author Kuali Rice Team (rice.collab@kuali.org) 071 * 072 */ 073 public class DocumentConfigurationViewAction extends KewKualiAction { 074 075 private static final Logger LOG = Logger.getLogger(DocumentConfigurationViewAction.class); 076 077 private PermissionService permissionService; 078 private RoleService roleService; 079 private ResponsibilityService responsibilityService; 080 private DocumentTypeService documentTypeService; 081 private DataDictionaryService dataDictionaryService; 082 private RouteNodeService routeNodeService; 083 private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; 084 private DocumentHelperService documentHelperService; 085 086 public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 087 populateForm( (DocumentConfigurationViewForm)form ); 088 return mapping.findForward("basic"); 089 } 090 091 protected void populateForm( DocumentConfigurationViewForm form ) { 092 if ( StringUtils.isNotEmpty( form.getDocumentTypeName() ) ) { 093 form.setDocumentType( getDocumentTypeService().findByName( form.getDocumentTypeName() ) ); 094 if ( form.getDocumentType() != null ) { 095 form.getDocumentType().getChildrenDocTypes(); 096 form.setAttributeLabels( new HashMap<String, String>() ); 097 populateRelatedDocuments( form ); 098 populatePermissions( form ); 099 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 }