001 /* 002 * Copyright 2008-2009 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.RouteNode; 028 import org.kuali.rice.kew.engine.node.service.RouteNodeService; 029 import org.kuali.rice.kew.service.KEWServiceLocator; 030 import org.kuali.rice.kew.util.KEWConstants; 031 import org.kuali.rice.kew.web.KewKualiAction; 032 import org.kuali.rice.kim.api.permission.Permission; 033 import org.kuali.rice.kim.api.responsibility.Responsibility; 034 import org.kuali.rice.kim.api.responsibility.ResponsibilityService; 035 import org.kuali.rice.kim.api.role.Role; 036 import org.kuali.rice.kim.api.role.RoleService; 037 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 038 import org.kuali.rice.kim.bo.impl.KimAttributes; 039 import org.kuali.rice.kim.bo.role.impl.KimPermissionImpl; 040 import org.kuali.rice.kim.impl.permission.PermissionBo; 041 import org.kuali.rice.kim.impl.permission.PermissionTemplateBo; 042 import org.kuali.rice.kim.impl.responsibility.ResponsibilityBo; 043 import org.kuali.rice.kim.service.PermissionService; 044 import org.kuali.rice.kim.util.KimConstants; 045 import org.kuali.rice.kns.service.KNSServiceLocator; 046 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 047 import org.kuali.rice.krad.service.DataDictionaryService; 048 import org.kuali.rice.krad.service.DocumentHelperService; 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.and; 065 import static org.kuali.rice.core.api.criteria.PredicateFactory.equal; 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(KimPermissionImpl.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 Map<String,String> searchCriteria = new HashMap<String,String>(); 147 searchCriteria.put("attributeName", "documentTypeName" ); 148 searchCriteria.put("active", "Y"); 149 // loop over the document hierarchy 150 Set<String> seenDocumentPermissions = new HashSet<String>(); 151 while ( docType != null) { 152 String documentTypeName = docType.getName(); 153 searchCriteria.put("detailCriteria", 154 KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME+"="+docType.getName() 155 ); 156 List<Permission> perms = getPermissionService().lookupPermissions(searchCriteria, true); 157 for ( Permission perm : perms ) { 158 PermissionBo permBo = PermissionBo.from(perm); 159 List<String> roleIds = getPermissionService().getRoleIdsForPermissions(Collections.singletonList(perm)); 160 permRoles.put( perm.getId(), getRoleService().getRoles(roleIds) ); 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", KEWConstants.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(), null); 218 form.getResponsibilityRoles().put( responsibility.getResponsibilityId(), getRoleService().getRoles(roleIds) ); 219 } 220 } 221 222 protected void addAttributeLabel( DocumentConfigurationViewForm form, String attributeName ) { 223 if ( !form.getAttributeLabels().containsKey(attributeName) ) { 224 form.getAttributeLabels().put(attributeName, 225 getDataDictionaryService().getAttributeLabel(KimAttributes.class, attributeName) ); 226 } 227 } 228 229 // loop over nodes 230 // if split node, push onto stack 231 // note the number of children, this is the number of times the join node needs to be found 232 // when join node found, return to last split on stack 233 // move to next child of the split 234 235 protected RouteNode flattenSplitNode( RouteNode splitNode, Map<String,RouteNode> nodes ) { 236 nodes.put( splitNode.getRouteNodeName(), splitNode ); 237 RouteNode joinNode = null; 238 239 for ( RouteNode nextNode : splitNode.getNextNodes() ) { 240 joinNode = flattenRouteNodes(nextNode, nodes); 241 } 242 243 if ( joinNode != null ) { 244 nodes.put( joinNode.getRouteNodeName(), joinNode ); 245 } 246 return joinNode; 247 } 248 249 /** 250 * @param node 251 * @param nodes 252 * @return The last node processed by this method. 253 */ 254 protected RouteNode flattenRouteNodes( RouteNode node, Map<String,RouteNode> nodes ) { 255 RouteNode lastProcessedNode = null; 256 // if we've seen the node before - skip, avoids infinite loop 257 if ( nodes.containsKey(node.getRouteNodeName()) ) { 258 return node; 259 } 260 261 if ( node.getNodeType().contains( "SplitNode" ) ) { // Hacky - but only way when the class may not be present in the KEW JVM 262 lastProcessedNode = flattenSplitNode(node, nodes); // special handling to process all split children before continuing 263 // now, process the join node's children 264 for ( RouteNode nextNode : lastProcessedNode.getNextNodes() ) { 265 lastProcessedNode = flattenRouteNodes(nextNode, nodes); 266 } 267 } else if ( node.getNodeType().contains( "JoinNode" ) ) { 268 lastProcessedNode = node; // skip, handled by the split node 269 } else { 270 // normal node, add to list and process all children 271 nodes.put(node.getRouteNodeName(), node); 272 for ( RouteNode nextNode : node.getNextNodes() ) { 273 lastProcessedNode = flattenRouteNodes(nextNode, nodes); 274 } 275 } 276 return lastProcessedNode; 277 } 278 279 @SuppressWarnings("unchecked") 280 public void populateRoutingResponsibilities( DocumentConfigurationViewForm form ) { 281 // pull all the responsibilities 282 // merge the data and attach to route levels 283 // pull the route levels and store on form 284 //List<RouteNode> routeNodes = getRouteNodeService().getFlattenedNodes(form.getDocumentType(), true); 285 RouteNode rootNode = ((List<org.kuali.rice.kew.engine.node.Process>)form.getDocumentType().getProcesses()).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 QueryByCriteria.Builder builder = QueryByCriteria.Builder.create(); 299 Predicate p = and( 300 equal("template.namespaceCode", KRADConstants.KUALI_RICE_WORKFLOW_NAMESPACE), 301 equal("template.name", KEWConstants.DEFAULT_RESPONSIBILITY_TEMPLATE_NAME), 302 equal("active", "Y"), 303 equal("attributes[documentTypeName]", docType.getName()) 304 ); 305 builder.setPredicates(p); 306 List<Responsibility> resps = getResponsibilityService().findResponsibilities(builder.build()).getResults(); 307 308 for ( Responsibility r : resps ) { 309 String routeNodeName = r.getAttributes().get(KimConstants.AttributeConstants.ROUTE_NODE_NAME); 310 if ( StringUtils.isNotBlank(routeNodeName) ) { 311 if ( !nodeToRespMap.containsKey( routeNodeName ) ) { 312 nodeToRespMap.put(routeNodeName, new ArrayList<ResponsibilityForDisplay>() ); 313 nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, false ) ); 314 } else { 315 // check if the responsibility in the existing list is for the current document 316 // if so, OK to add. Otherwise, a lower level document has overridden this 317 // responsibility (since we are walking up the hierarchy 318 if ( nodeToRespMap.get(routeNodeName).get(0).getDetails().get( KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME ).equals(docType.getName() ) ) { 319 nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, false ) ); 320 } else { // doc type name did not match, mark as overridden 321 nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, true ) ); 322 } 323 } 324 responsibilities.add(r); 325 } 326 } 327 docType = docType.getParentDocType(); 328 } 329 form.setResponsibilityMap( nodeToRespMap ); 330 331 Map<String,List<Role>> respToRoleMap = new HashMap<String, List<Role>>(); 332 for (Responsibility responsibility : responsibilities ) { 333 List<String> roleIds = getResponsibilityService().getRoleIdsForResponsibility(responsibility.getId(), null); 334 respToRoleMap.put( responsibility.getId(), getRoleService().getRoles(roleIds) ); 335 } 336 form.setResponsibilityRoles( respToRoleMap ); 337 } 338 339 /** 340 * @see org.kuali.rice.krad.web.struts.action.KualiAction#toggleTab(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 341 */ 342 @Override 343 public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 344 // Repopulating the form is necessary when toggling tab states on the server side. 345 ActionForward actionForward = super.toggleTab(mapping, form, request, response); 346 populateForm( (DocumentConfigurationViewForm)form ); 347 return actionForward; 348 } 349 350 /** 351 * Internal delegate class to wrap a responsibility and add an overridden flag. 352 */ 353 public static class ResponsibilityForDisplay { 354 355 private Responsibility resp; 356 private boolean overridden = false; 357 358 public ResponsibilityForDisplay( Responsibility resp, boolean overridden ) { 359 this.resp = resp; 360 this.overridden = overridden; 361 } 362 363 /** 364 * @return the resp 365 */ 366 Responsibility getResp() { 367 return this.resp; 368 } 369 370 public boolean isOverridden() { 371 return this.overridden; 372 } 373 374 public void setOverridden(boolean overridden) { 375 this.overridden = overridden; 376 } 377 378 public Map<String, String> getDetails() { 379 return new HashMap<String, String>(this.resp.getAttributes()); 380 } 381 382 public String getName() { 383 return this.resp.getName(); 384 } 385 386 public String getNamespaceCode() { 387 return this.resp.getNamespaceCode(); 388 } 389 390 public String getResponsibilityId() { 391 return this.resp.getId(); 392 } 393 } 394 395 public static class PermissionForDisplay { 396 private PermissionBo perm; 397 private boolean overridden = false; 398 399 public PermissionForDisplay( PermissionBo perm, boolean overridden ) { 400 this.perm = perm; 401 this.overridden = overridden; 402 } 403 public boolean isOverridden() { 404 return this.overridden; 405 } 406 407 public void setOverridden(boolean overridden) { 408 this.overridden = overridden; 409 } 410 public Map<String, String> getDetails() { 411 return this.perm.getDetails(); 412 } 413 public String getName() { 414 return this.perm.getName(); 415 } 416 public String getNamespaceCode() { 417 return this.perm.getNamespaceCode(); 418 } 419 public String getPermissionId() { 420 return this.perm.getId(); 421 } 422 public PermissionTemplateBo getTemplate() { 423 return this.perm.getTemplate(); 424 } 425 426 } 427 428 /** 429 * @return the permissionService 430 */ 431 public PermissionService getPermissionService() { 432 if ( permissionService == null ) { 433 permissionService = KimApiServiceLocator.getPermissionService(); 434 } 435 return permissionService; 436 } 437 438 /** 439 * @return the roleService 440 */ 441 public RoleService getRoleService() { 442 if ( roleService == null ) { 443 roleService = KimApiServiceLocator.getRoleService(); 444 } 445 return roleService; 446 } 447 448 /** 449 * @return the responsibilityService 450 */ 451 public ResponsibilityService getResponsibilityService() { 452 if ( responsibilityService == null ) { 453 responsibilityService = KimApiServiceLocator.getResponsibilityService(); 454 } 455 return responsibilityService; 456 } 457 458 /** 459 * @return the documentTypeService 460 */ 461 public DocumentTypeService getDocumentTypeService() { 462 if ( documentTypeService == null ) { 463 documentTypeService = KEWServiceLocator.getDocumentTypeService(); 464 } 465 return documentTypeService; 466 } 467 468 public DataDictionaryService getDataDictionaryService() { 469 if(dataDictionaryService == null){ 470 dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 471 } 472 return dataDictionaryService; 473 } 474 475 public RouteNodeService getRouteNodeService() { 476 if ( routeNodeService == null ) { 477 routeNodeService = KEWServiceLocator.getRouteNodeService(); 478 } 479 return routeNodeService; 480 } 481 482 public DocumentHelperService getDocumentHelperService() { 483 if(documentHelperService == null){ 484 documentHelperService = KRADServiceLocatorWeb.getDocumentHelperService(); 485 } 486 return documentHelperService; 487 } 488 489 public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() { 490 if(maintenanceDocumentDictionaryService == null){ 491 maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService(); 492 } 493 return maintenanceDocumentDictionaryService; 494 } 495 496 }