001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.kew.rule.web; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.log4j.Logger; 020import org.apache.struts.action.ActionForm; 021import org.apache.struts.action.ActionForward; 022import org.apache.struts.action.ActionMapping; 023import org.kuali.rice.core.api.criteria.Predicate; 024import org.kuali.rice.core.api.criteria.QueryByCriteria; 025import org.kuali.rice.kew.doctype.bo.DocumentType; 026import org.kuali.rice.kew.doctype.service.DocumentTypeService; 027import org.kuali.rice.kew.engine.node.ProcessDefinitionBo; 028import org.kuali.rice.kew.engine.node.RouteNode; 029import org.kuali.rice.kew.engine.node.service.RouteNodeService; 030import org.kuali.rice.kew.service.KEWServiceLocator; 031import org.kuali.rice.kew.web.KewKualiAction; 032import org.kuali.rice.kim.api.KimConstants; 033import org.kuali.rice.kim.api.permission.Permission; 034import org.kuali.rice.kim.api.permission.PermissionService; 035import org.kuali.rice.kim.api.responsibility.Responsibility; 036import org.kuali.rice.kim.api.responsibility.ResponsibilityService; 037import org.kuali.rice.kim.api.role.Role; 038import org.kuali.rice.kim.api.role.RoleService; 039import org.kuali.rice.kim.api.services.KimApiServiceLocator; 040import org.kuali.rice.kim.bo.impl.KimAttributes; 041import org.kuali.rice.kim.impl.permission.GenericPermissionBo; 042import org.kuali.rice.kim.impl.permission.PermissionBo; 043import org.kuali.rice.kim.impl.permission.PermissionTemplateBo; 044import org.kuali.rice.kim.impl.responsibility.ReviewResponsibilityBo; 045import org.kuali.rice.kns.service.DocumentHelperService; 046import org.kuali.rice.kns.service.KNSServiceLocator; 047import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 048import org.kuali.rice.krad.service.DataDictionaryService; 049import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 050import org.kuali.rice.krad.util.GlobalVariables; 051 052import javax.servlet.http.HttpServletRequest; 053import javax.servlet.http.HttpServletResponse; 054import java.util.ArrayList; 055import java.util.HashMap; 056import java.util.HashSet; 057import java.util.LinkedHashMap; 058import java.util.List; 059import java.util.Map; 060import java.util.Set; 061 062import static org.kuali.rice.core.api.criteria.PredicateFactory.and; 063import static org.kuali.rice.core.api.criteria.PredicateFactory.equal; 064 065 066/** 067 * This is a description of what this class does - kellerj don't forget to fill this in. 068 * 069 * @author Kuali Rice Team (rice.collab@kuali.org) 070 * 071 */ 072public class DocumentConfigurationViewAction extends KewKualiAction { 073 074 private static final Logger LOG = Logger.getLogger(DocumentConfigurationViewAction.class); 075 076 private PermissionService permissionService; 077 private RoleService roleService; 078 private ResponsibilityService responsibilityService; 079 private DocumentTypeService documentTypeService; 080 private DataDictionaryService dataDictionaryService; 081 private RouteNodeService routeNodeService; 082 private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; 083 private DocumentHelperService documentHelperService; 084 085 public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 086 populateForm( (DocumentConfigurationViewForm)form ); 087 return mapping.findForward("basic"); 088 } 089 090 protected void populateForm( DocumentConfigurationViewForm form ) { 091 if ( StringUtils.isNotEmpty( form.getDocumentTypeName() ) ) { 092 form.setDocumentType( getDocumentTypeService().findByName( form.getDocumentTypeName() ) ); 093 if ( form.getDocumentType() != null ) { 094 form.getDocumentType().getChildrenDocTypes(); 095 form.setAttributeLabels( new HashMap<String, String>() ); 096 populateRelatedDocuments( form ); 097 populatePermissions( form ); 098 populateRoutingResponsibilities( form ); 099 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}