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 }