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    }