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.apache.struts.action.ActionMessages; 024import org.kuali.rice.core.api.config.property.ConfigContext; 025import org.kuali.rice.core.api.criteria.Predicate; 026import org.kuali.rice.core.api.criteria.QueryByCriteria; 027import org.kuali.rice.kew.doctype.bo.DocumentType; 028import org.kuali.rice.kew.doctype.service.DocumentTypeService; 029import org.kuali.rice.kew.engine.node.BranchPrototype; 030import org.kuali.rice.kew.engine.node.RouteNode; 031import org.kuali.rice.kew.engine.node.RouteNodeConfigParam; 032import org.kuali.rice.kew.rule.RuleBaseValues; 033import org.kuali.rice.kew.rule.bo.RuleTemplateBo; 034import org.kuali.rice.kew.rule.service.RuleServiceInternal; 035import org.kuali.rice.kew.service.KEWServiceLocator; 036import org.kuali.rice.kew.api.KewApiConstants; 037import org.kuali.rice.kew.web.KewKualiAction; 038import org.kuali.rice.kim.api.KimConstants; 039import org.kuali.rice.kim.api.group.Group; 040import org.kuali.rice.kim.api.permission.Permission; 041import org.kuali.rice.kim.api.responsibility.Responsibility; 042import org.kuali.rice.kim.api.services.KimApiServiceLocator; 043import org.kuali.rice.kns.service.DocumentHelperService; 044import org.kuali.rice.kns.service.KNSServiceLocator; 045import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 046import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 047import org.kuali.rice.krad.util.GlobalVariables; 048import org.kuali.rice.krad.util.KRADConstants; 049 050import javax.servlet.http.HttpServletRequest; 051import javax.servlet.http.HttpServletResponse; 052import java.util.ArrayList; 053import java.util.Collections; 054import java.util.Comparator; 055import java.util.HashMap; 056import java.util.Iterator; 057import java.util.List; 058 059import static org.kuali.rice.core.api.criteria.PredicateFactory.and; 060import static org.kuali.rice.core.api.criteria.PredicateFactory.equal; 061 062 063/** 064 * A Struts Action for building and interacting with the Rule Quick Links. 065 * 066 * @author Kuali Rice Team (rice.collab@kuali.org) 067 */ 068public class RuleQuickLinksAction extends KewKualiAction { 069 070 private static final Logger LOG = Logger.getLogger(RuleQuickLinksAction.class); 071 072 private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; 073 private DocumentHelperService documentHelperService; 074 075 public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 076 makeLookupPathParam(mapping, request); 077 establishRequiredState(request, form); 078 return mapping.findForward("basic"); 079 } 080 081 @SuppressWarnings("unchecked") 082 public ActionMessages establishRequiredState(HttpServletRequest request, ActionForm form) throws Exception { 083 RuleQuickLinksForm qlForm = (RuleQuickLinksForm) form; 084 List<DocumentType> documentTypes; 085 if (qlForm.getRootDocTypeName() != null) { 086 documentTypes = new ArrayList<DocumentType>(); 087 DocumentType docType = getDocumentTypeService().findByName(qlForm.getRootDocTypeName()); 088 documentTypes.add(docType); 089 request.setAttribute("renderOpened", Boolean.TRUE); 090 } else { 091 documentTypes = getDocumentTypeService().findAllCurrentRootDocuments(); 092 } 093 qlForm.setDocumentTypeQuickLinksStructures(getDocumentTypeDataStructure(documentTypes)); 094 int shouldDisplayCount = 0; 095 for ( DocumentTypeQuickLinksStructure dt : qlForm.getDocumentTypeQuickLinksStructures() ) { 096 if ( dt.isShouldDisplay() ) { 097 shouldDisplayCount++; 098 } 099 } 100 if ( shouldDisplayCount == 1 ) { 101 request.setAttribute("renderOpened", Boolean.TRUE); 102 } 103 String documentTypeName = getMaintenanceDocumentDictionaryService().getDocumentTypeName(DocumentType.class); 104 try { 105 if ((documentTypeName != null) && getDocumentHelperService().getDocumentAuthorizer(documentTypeName).canInitiate(documentTypeName, GlobalVariables.getUserSession().getPerson())) { 106 qlForm.setCanInitiateDocumentTypeDocument( true ); 107 } 108 } catch (Exception ex) { 109 // just skip - and don't display links 110 LOG.error( "Unable to check initiation permission for "+ documentTypeName, ex ); 111 } 112 113 return null; 114 } 115 116 public ActionForward addDelegationRule(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 117 ActionForward result = null; 118 119 String ruleTemplateId = request.getParameter("delegationRule.ruleTemplate.id"); 120 String docTypeName = request.getParameter("delegationRule.documentType.name"); 121 List rules = getRuleService().search(docTypeName, null, ruleTemplateId, "", null, null, Boolean.FALSE, Boolean.TRUE, new HashMap(), null); 122 123 if (rules.size() == 1) { 124 RuleBaseValues rule = (RuleBaseValues)rules.get(0); 125 String url = ConfigContext.getCurrentContextConfig().getKEWBaseURL() + 126 "/DelegateRule.do?methodToCall=start" + 127 "&parentRuleId=" + rule.getId(); 128 result = new ActionForward(url, true); 129 } else { 130 makeLookupPathParam(mapping, request); 131 result = new ActionForward(ConfigContext.getCurrentContextConfig().getKRBaseURL() + 132 "/lookup.do?methodToCall=start&"+ stripMethodToCall(request.getQueryString()), true); 133 } 134 135 return result; 136 } 137 138 private List getDocumentTypeDataStructure(List rootDocuments) { 139 List documentTypeQuickLinksStructures = new ArrayList(); 140 for (Iterator iter = rootDocuments.iterator(); iter.hasNext();) { 141 DocumentTypeQuickLinksStructure quickLinkStruct =new DocumentTypeQuickLinksStructure((DocumentType) iter.next()); 142 if (! quickLinkStruct.getFlattenedNodes().isEmpty() || ! quickLinkStruct.getChildrenDocumentTypes().isEmpty()) { 143 documentTypeQuickLinksStructures.add(quickLinkStruct); 144 } 145 146 } 147 148 return documentTypeQuickLinksStructures; 149 } 150 151 152 /** 153 * A bean to hold a DocumentType with its flattened nodes for rendering purposes 154 * on the quick links. 155 * 156 * @author Kuali Rice Team (rice.collab@kuali.org) 157 */ 158 public static class DocumentTypeQuickLinksStructure { 159 private DocumentType documentType; 160 private List<RouteNode> flattenedNodes = new ArrayList<RouteNode>(); 161 private List<DocumentTypeQuickLinksStructure> childrenDocumentTypes = new ArrayList<DocumentTypeQuickLinksStructure>(); 162 private List<Permission> permissions = null; 163 164 private DocumentTypeQuickLinksStructure(DocumentType documentType) { 165 this.documentType = documentType; 166 if ( documentType != null ) { 167 List<RouteNode> tempFlattenedNodes = KEWServiceLocator.getRouteNodeService() 168 .getFlattenedNodes( documentType, true ); 169 for ( RouteNode routeNode : tempFlattenedNodes ) { 170 if ( routeNode.isFlexRM() || routeNode.isRoleNode() ) { 171 flattenedNodes.add( new RouteNodeForDisplay( routeNode ) ); 172 } 173 } 174 Collections.sort(flattenedNodes,new Comparator() { 175 public int compare(Object o1, Object o2) { 176 return ( ((RouteNode)o1).getRouteNodeName().compareTo(((RouteNode)o2).getRouteNodeName())); 177 } 178 }); 179 for ( Iterator<DocumentType> iter = documentType.getChildrenDocTypes().iterator(); iter.hasNext(); ) { 180 childrenDocumentTypes.add( new DocumentTypeQuickLinksStructure( iter.next() ) ); 181 } 182 Collections.sort(childrenDocumentTypes,new Comparator() { 183 public int compare(Object o1, Object o2) { 184 return ( ((DocumentTypeQuickLinksStructure)o1).documentType.getLabel().compareTo(((DocumentTypeQuickLinksStructure)o2).documentType.getLabel())); 185 } 186 }); 187 } 188 } 189 190 public List getChildrenDocumentTypes() { 191 return childrenDocumentTypes; 192 } 193 public void setChildrenDocumentTypes(List<DocumentTypeQuickLinksStructure> childrenDocumentTypes) { 194 this.childrenDocumentTypes = childrenDocumentTypes; 195 } 196 public DocumentType getDocumentType() { 197 return documentType; 198 } 199 public void setDocumentType(DocumentType documentType) { 200 this.documentType = documentType; 201 } 202 public List getFlattenedNodes() { 203 return flattenedNodes; 204 } 205 public void setFlattenedNodes(List<RouteNode> flattenedNodes) { 206 this.flattenedNodes = flattenedNodes; 207 } 208 public boolean isShouldDisplay() { 209// if (flattenedNodes.isEmpty()) { 210// for (DocumentTypeQuickLinksStructure docType : childrenDocumentTypes) { 211// if (docType.isShouldDisplay()) { 212// return true; 213// } 214// } 215// return false; 216// } 217 return true; 218 } 219 220 public List<Permission> getPermissions() { 221 if ( permissions == null ) { 222 Predicate p = and( 223 equal("attributeName", "documentTypeName"), 224 equal("active", "Y"), 225 equal("detailCriteria", 226 KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME+"="+getDocumentType().getName())); 227 permissions = KimApiServiceLocator.getPermissionService().findPermissions( QueryByCriteria.Builder.fromPredicates(p)).getResults(); 228// sqlLogger.setLevel( Level.INFO ); 229 } 230 return permissions; 231 } 232 233 public boolean isHasRelatedPermissions() { 234 return !getPermissions().isEmpty(); 235 } 236 237 public int getRelatedPermissionCount() { 238 return getPermissions().size(); 239 } 240 } 241 242 public static class RouteNodeForDisplay extends RouteNode { 243 private static final long serialVersionUID = 1L; 244 private RouteNode baseNode; 245 246 public RouteNodeForDisplay( RouteNode baseNode ) { 247 this.baseNode = baseNode; 248 } 249 250 public boolean equals(Object obj) { 251 return this.baseNode.equals(obj); 252 } 253 254 public String getActivationType() { 255 return this.baseNode.getActivationType(); 256 } 257 258 public BranchPrototype getBranch() { 259 return this.baseNode.getBranch(); 260 } 261 262 public List<RouteNodeConfigParam> getConfigParams() { 263 return this.baseNode.getConfigParams(); 264 } 265 266 public String getContentFragment() { 267 return this.baseNode.getContentFragment(); 268 } 269 270 public DocumentType getDocumentType() { 271 return this.baseNode.getDocumentType(); 272 } 273 274 public String getDocumentTypeId() { 275 return this.baseNode.getDocumentTypeId(); 276 } 277 public Group getExceptionWorkgroup() { 278 return this.baseNode.getExceptionWorkgroup(); 279 } 280 public String getExceptionWorkgroupId() { 281 return this.baseNode.getExceptionWorkgroupId(); 282 } 283 public String getExceptionWorkgroupName() { 284 return this.baseNode.getExceptionWorkgroupName(); 285 } 286 public Boolean getFinalApprovalInd() { 287 return this.baseNode.getFinalApprovalInd(); 288 } 289 public Integer getLockVerNbr() { 290 return this.baseNode.getLockVerNbr(); 291 } 292 public Boolean getMandatoryRouteInd() { 293 return this.baseNode.getMandatoryRouteInd(); 294 } 295 public List<RouteNode> getNextNodes() { 296 return this.baseNode.getNextNodes(); 297 } 298 public String getNodeType() { 299 return this.baseNode.getNodeType(); 300 } 301 public List<RouteNode> getPreviousNodes() { 302 return this.baseNode.getPreviousNodes(); 303 } 304 public String getRouteMethodCode() { 305 return this.baseNode.getRouteMethodCode(); 306 } 307 public String getRouteMethodName() { 308 return this.baseNode.getRouteMethodName(); 309 } 310 public String getRouteNodeId() { 311 return this.baseNode.getRouteNodeId(); 312 } 313 public String getRouteNodeName() { 314 return this.baseNode.getRouteNodeName(); 315 } 316 public RuleTemplateBo getRuleTemplate() { 317 return this.baseNode.getRuleTemplate(); 318 } 319 public int hashCode() { 320 return this.baseNode.hashCode(); 321 } 322 public boolean isExceptionGroupDefined() { 323 return this.baseNode.isExceptionGroupDefined(); 324 } 325 public boolean isFlexRM() { 326 return this.baseNode.isFlexRM(); 327 } 328 public boolean isRoleNode() { 329 return this.baseNode.isRoleNode(); 330 } 331 public String toString() { 332 return this.baseNode.toString(); 333 } 334 335 private List<Responsibility> responsibilities = null; 336 337 public List<Responsibility> getResponsibilities() { 338 if ( responsibilities == null ) { 339 QueryByCriteria.Builder builder = QueryByCriteria.Builder.create(); 340 Predicate p = and( 341 equal("template.namespaceCode", KRADConstants.KUALI_RICE_WORKFLOW_NAMESPACE), 342 equal("template.name", KewApiConstants.DEFAULT_RESPONSIBILITY_TEMPLATE_NAME), 343 equal("active", "Y"), 344 equal("attributes[documentTypeName]", getDocumentType().getName()) 345 // KULRICE-8538 -- Check the route node by looping through the results below. If it is added 346 // into the predicate, no rows are ever returned. 347 // equal("attributes[routeNodeName]", getRouteNodeName()) 348 ); 349 350 351 builder.setPredicates(p); 352 353 List<Responsibility> possibleResponsibilities = 354 KimApiServiceLocator.getResponsibilityService().findResponsibilities(builder.build()).getResults(); 355 356 if ( !possibleResponsibilities.isEmpty() ) { 357 for ( Responsibility resp : possibleResponsibilities ) { 358 String routeNodeName = resp.getAttributes().get( KimConstants.AttributeConstants.ROUTE_NODE_NAME); 359 if (StringUtils.isNotEmpty(routeNodeName) && StringUtils.equals(routeNodeName, getRouteNodeName())){ 360 responsibilities.add(resp); 361 } 362 } 363 } else { 364 responsibilities = possibleResponsibilities; 365 } 366 } 367 return responsibilities; 368 } 369 370 public int getResponsibilityCount() { 371 return getResponsibilities().size(); 372 } 373 374 public boolean isHasResponsibility() { 375 return !getResponsibilities().isEmpty(); 376 } 377 } 378 379 private void makeLookupPathParam(ActionMapping mapping, HttpServletRequest request) { 380 String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + mapping.getModuleConfig().getPrefix(); 381 request.setAttribute("basePath", basePath); 382 } 383 384 private String stripMethodToCall(String queryString) { 385 return queryString.replaceAll("methodToCall=addDelegationRule&", ""); 386 } 387 388 private DocumentTypeService getDocumentTypeService() { 389 return KEWServiceLocator.getDocumentTypeService(); 390 } 391 392 private RuleServiceInternal getRuleService() { 393 return KEWServiceLocator.getRuleService(); 394 } 395 396 public DocumentHelperService getDocumentHelperService() { 397 if(documentHelperService == null){ 398 documentHelperService = KNSServiceLocator.getDocumentHelperService(); 399 } 400 return documentHelperService; 401 } 402 403 public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() { 404 if(maintenanceDocumentDictionaryService == null){ 405 maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService(); 406 } 407 return maintenanceDocumentDictionaryService; 408 } 409 410 @Override 411 public ActionForward toggleTab(ActionMapping mapping, ActionForm form, 412 HttpServletRequest request, HttpServletResponse response) 413 throws Exception { 414 415 establishRequiredState(request, form); 416 return super.toggleTab(mapping, form, request, response); 417 } 418 419}