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 }