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