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.web.KewKualiAction;
032import org.kuali.rice.kim.api.KimConstants;
033import org.kuali.rice.kim.api.permission.Permission;
034import org.kuali.rice.kim.api.permission.PermissionService;
035import org.kuali.rice.kim.api.responsibility.Responsibility;
036import org.kuali.rice.kim.api.responsibility.ResponsibilityService;
037import org.kuali.rice.kim.api.role.Role;
038import org.kuali.rice.kim.api.role.RoleService;
039import org.kuali.rice.kim.api.services.KimApiServiceLocator;
040import org.kuali.rice.kim.bo.impl.KimAttributes;
041import org.kuali.rice.kim.impl.permission.GenericPermissionBo;
042import org.kuali.rice.kim.impl.permission.PermissionBo;
043import org.kuali.rice.kim.impl.permission.PermissionTemplateBo;
044import org.kuali.rice.kim.impl.responsibility.ReviewResponsibilityBo;
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;
051
052import javax.servlet.http.HttpServletRequest;
053import javax.servlet.http.HttpServletResponse;
054import java.util.ArrayList;
055import java.util.HashMap;
056import java.util.HashSet;
057import java.util.LinkedHashMap;
058import java.util.List;
059import java.util.Map;
060import java.util.Set;
061
062import static org.kuali.rice.core.api.criteria.PredicateFactory.and;
063import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
064
065
066/**
067 * This is a description of what this class does - kellerj don't forget to fill this in. 
068 * 
069 * @author Kuali Rice Team (rice.collab@kuali.org)
070 *
071 */
072public class DocumentConfigurationViewAction extends KewKualiAction {
073
074        private static final Logger LOG = Logger.getLogger(DocumentConfigurationViewAction.class);
075        
076        private PermissionService permissionService;
077        private RoleService roleService;
078        private ResponsibilityService responsibilityService;
079        private DocumentTypeService documentTypeService;
080        private DataDictionaryService dataDictionaryService;
081        private RouteNodeService routeNodeService;
082        private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
083        private DocumentHelperService documentHelperService;
084        
085    public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
086        populateForm( (DocumentConfigurationViewForm)form );
087        return mapping.findForward("basic");
088    }
089    
090    protected void populateForm( DocumentConfigurationViewForm form ) {
091        if ( StringUtils.isNotEmpty( form.getDocumentTypeName() ) ) {
092                form.setDocumentType( getDocumentTypeService().findByName( form.getDocumentTypeName() ) ); 
093                if ( form.getDocumentType() != null ) {
094                        form.getDocumentType().getChildrenDocTypes();
095                        form.setAttributeLabels( new HashMap<String, String>() );
096                        populateRelatedDocuments( form );
097                        populatePermissions( form );
098                        populateRoutingResponsibilities( form );
099                        populateRoutingExceptionResponsibility( form );
100                        checkPermissions( form );
101                }
102        }
103    }
104
105    protected void checkPermissions( DocumentConfigurationViewForm form ) {
106        String docTypeDocumentType = getMaintenanceDocumentDictionaryService().getDocumentTypeName(DocumentType.class);
107        try {
108            if ((docTypeDocumentType != null) && getDocumentHelperService().getDocumentAuthorizer(docTypeDocumentType).canInitiate(docTypeDocumentType, GlobalVariables.getUserSession().getPerson())) {
109                form.setCanInitiateDocumentTypeDocument( true );
110            }
111        } catch (Exception ex) {
112                        // just skip - and don't display links
113                LOG.error( "Unable to check DocumentType initiation permission for "+ docTypeDocumentType, ex );
114                }
115        String permissionDocumentType = getMaintenanceDocumentDictionaryService().getDocumentTypeName(GenericPermissionBo.class);
116        try {
117            if ((permissionDocumentType != null) && getDocumentHelperService().getDocumentAuthorizer(permissionDocumentType).canInitiate(permissionDocumentType, GlobalVariables.getUserSession().getPerson())) {
118                form.setCanInitiatePermissionDocument( true );
119            }
120        } catch (Exception ex) {
121                        // just skip - and don't display links
122                LOG.error( "Unable to check Permission initiation permission for "+ permissionDocumentType, ex );
123                }
124        String responsibilityDocumentType = getMaintenanceDocumentDictionaryService().getDocumentTypeName(ReviewResponsibilityBo.class);
125        try {
126            if ((responsibilityDocumentType != null) && getDocumentHelperService().getDocumentAuthorizer(responsibilityDocumentType).canInitiate(responsibilityDocumentType, GlobalVariables.getUserSession().getPerson())) {
127                form.setCanInitiateResponsibilityDocument( true );
128            }
129        } catch (Exception ex) {
130                        // just skip - and don't display links
131                LOG.error( "Unable to check Responsibility initiation permission for "+ responsibilityDocumentType, ex );
132                }
133    }
134    
135    @SuppressWarnings("unchecked")
136        public void populateRelatedDocuments( DocumentConfigurationViewForm form ) {
137        form.setParentDocumentType( form.getDocumentType().getParentDocType() );
138        form.setChildDocumentTypes( new ArrayList<DocumentType>( form.getDocumentType().getChildrenDocTypes() ) );
139    }
140    
141        public void populatePermissions( DocumentConfigurationViewForm form ) {
142                
143                DocumentType docType = form.getDocumentType();
144                Map<String,List<Role>> permRoles = new HashMap<String, List<Role>>();
145                // loop over the document hierarchy
146                Set<String> seenDocumentPermissions = new HashSet<String>();
147                while ( docType != null) {
148                        String documentTypeName = docType.getName();
149            Predicate p = and(
150                equal("active", Boolean.TRUE),
151                equal("attributes[" + KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME + "]", docType.getName()));
152                        List<Permission> perms = getPermissionService().findPermissions(QueryByCriteria.Builder.fromPredicates(p)).getResults();
153                        for ( Permission perm : perms ) {
154                PermissionBo permBo = PermissionBo.from(perm);
155                                List<String> roleIds = getPermissionService().getRoleIdsForPermission(perm.getNamespaceCode(),
156                        perm.getName());
157                if (!roleIds.isEmpty()) {
158                                    permRoles.put( perm.getId(), getRoleService().getRoles(roleIds) );
159                }
160                                for ( String attributeName : permBo.getDetails().keySet() ) {
161                                        addAttributeLabel(form, attributeName);
162                                }
163                        }
164                        // show the section if the current document or permissions exist
165                        if ( perms.size() > 0 || documentTypeName.equals( form.getDocumentTypeName() ) ) {
166                                ArrayList<PermissionForDisplay> dispPerms = new ArrayList<PermissionForDisplay>( perms.size() );
167                                for ( Permission perm : perms ) {
168                    PermissionBo permBo = PermissionBo.from(perm);
169                                        if ( permBo.getDetails().size() == 1  ) { // only the document type
170                                                // this is a document type-specific permission, check if seen earlier
171                                                if ( seenDocumentPermissions.contains(perm.getTemplate().getNamespaceCode()+"|"+perm.getTemplate().getName()) ) {
172                                                        dispPerms.add( new PermissionForDisplay( permBo, true ) );
173                                                } else {
174                                                        dispPerms.add( new PermissionForDisplay( permBo, false ) );
175                                                        seenDocumentPermissions.add(perm.getTemplate().getNamespaceCode()+"|"+perm.getTemplate().getName());
176                                                }
177                                        } else {
178                                                // other attributes, can't determine whether this is overridden at another level
179                                                dispPerms.add( new PermissionForDisplay( permBo, false ) );
180                                        }
181                                }
182                                form.setPermissionsForDocumentType(documentTypeName, dispPerms );
183                                form.addDocumentType(documentTypeName);
184                        }
185                        docType = docType.getParentDocType();                   
186                }
187                
188                form.setPermissionRoles( permRoles );
189        }
190        
191        protected void populateRoutingExceptionResponsibility( DocumentConfigurationViewForm form ) {   
192                DocumentType docType = form.getDocumentType();
193                List<ResponsibilityForDisplay> responsibilities = new ArrayList<ResponsibilityForDisplay>();
194                while ( docType != null) {
195                        List<Responsibility> resps = getResponsibilityService().findWorkflowExceptionResponsibilities(docType.getName());
196                        
197                        for ( Responsibility r : resps ) {
198                                if ( responsibilities.isEmpty() ) {
199                                        responsibilities.add( new ResponsibilityForDisplay( r, false ) );
200                                } else {
201                                        responsibilities.add( new ResponsibilityForDisplay( r, true ) );
202                                }
203                        }
204                        docType = docType.getParentDocType();                   
205                }
206                form.setExceptionResponsibilities( responsibilities );
207                for ( ResponsibilityForDisplay responsibility : responsibilities ) {
208                        List<String> roleIds = getResponsibilityService().getRoleIdsForResponsibility(responsibility.getResp().getId());
209            if (!roleIds.isEmpty()) {
210                            form.getResponsibilityRoles().put( responsibility.getResponsibilityId(), getRoleService().getRoles(roleIds) );
211            }
212                }
213        }
214
215        protected void addAttributeLabel( DocumentConfigurationViewForm form, String attributeName ) {
216                if ( !form.getAttributeLabels().containsKey(attributeName) ) {
217                        form.getAttributeLabels().put(attributeName, 
218                                        getDataDictionaryService().getAttributeLabel(KimAttributes.class, attributeName) );
219                }
220        }
221        
222        // loop over nodes
223        // if split node, push onto stack
224                // note the number of children, this is the number of times the join node needs to be found
225        // when join node found, return to last split on stack
226                // move to next child of the split
227        
228        protected RouteNode flattenSplitNode( RouteNode splitNode, Map<String,RouteNode> nodes ) {
229                nodes.put( splitNode.getRouteNodeName(), splitNode );
230                RouteNode joinNode = null;
231                
232                for ( RouteNode nextNode : splitNode.getNextNodes() ) {
233                        joinNode = flattenRouteNodes(nextNode, nodes);
234                }
235                
236                if ( joinNode != null ) {
237                        nodes.put( joinNode.getRouteNodeName(), joinNode );
238                }
239                return joinNode;
240        }
241        
242        /**
243         * @param node
244         * @param nodes
245         * @return The last node processed by this method.
246         */
247        protected RouteNode flattenRouteNodes( RouteNode node, Map<String,RouteNode> nodes ) {
248                RouteNode lastProcessedNode = null;
249        if (node != null) {
250            // if we've seen the node before - skip, avoids infinite loop
251            if ( nodes.containsKey(node.getRouteNodeName()) ) {
252                return node;
253            }
254                
255            if ( node.getNodeType().contains( "SplitNode" ) ) { // Hacky - but only way when the class may not be present in the KEW JVM
256                lastProcessedNode = flattenSplitNode(node, nodes); // special handling to process all split children before continuing
257                // now, process the join node's children
258                if (lastProcessedNode != null) {
259                    for ( RouteNode nextNode : lastProcessedNode.getNextNodes() ) {
260                        lastProcessedNode = flattenRouteNodes(nextNode, nodes);
261                    }
262                }
263            } else if ( node.getNodeType().contains( "JoinNode" ) ) {
264                lastProcessedNode = node; // skip, handled by the split node
265            } else {
266                // normal node, add to list and process all children
267                nodes.put(node.getRouteNodeName(), node);
268                for ( RouteNode nextNode : node.getNextNodes() ) {
269                    lastProcessedNode = flattenRouteNodes(nextNode, nodes);
270                }
271            }
272        }
273                return lastProcessedNode;
274        }
275        
276        @SuppressWarnings("unchecked")
277        public void populateRoutingResponsibilities( DocumentConfigurationViewForm form ) {
278                // pull all the responsibilities
279                // merge the data and attach to route levels
280                // pull the route levels and store on form
281                //List<RouteNode> routeNodes = getRouteNodeService().getFlattenedNodes(form.getDocumentType(), true);
282        Map<String,List<Role>> respToRoleMap = new HashMap<String, List<Role>>();
283        List<ProcessDefinitionBo> processes = (List<ProcessDefinitionBo>)form.getDocumentType().getProcesses();
284        if (!(processes.isEmpty())) {
285            RouteNode rootNode = processes.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                List<Responsibility> resps = getResponsibilityService().findWorkflowResponsibilities(docType.getName());
299                        
300                for ( Responsibility r : resps ) {
301                    String routeNodeName = r.getAttributes().get(KimConstants.AttributeConstants.ROUTE_NODE_NAME);
302                    if ( StringUtils.isNotBlank(routeNodeName) ) {
303                        if ( !nodeToRespMap.containsKey( routeNodeName ) ) {
304                            nodeToRespMap.put(routeNodeName, new ArrayList<ResponsibilityForDisplay>() );
305                            nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, false ) );
306                        } else {
307                            // check if the responsibility in the existing list is for the current document
308                            // if so, OK to add.  Otherwise, a lower level document has overridden this
309                            // responsibility (since we are walking up the hierarchy
310                            if ( nodeToRespMap.get(routeNodeName).get(0).getDetails().get( KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME ).equals(docType.getName() ) ) {
311                                nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, false ) );
312                            } else { // doc type name did not match, mark as overridden
313                                nodeToRespMap.get(routeNodeName).add( new ResponsibilityForDisplay( r, true ) );
314                            }
315                        }
316                        responsibilities.add(r);
317                    }
318                }
319                docType = docType.getParentDocType();                   
320            }
321            form.setResponsibilityMap( nodeToRespMap );
322                
323                    for (Responsibility responsibility : responsibilities ) {
324                        List<String> roleIds = getResponsibilityService().getRoleIdsForResponsibility(responsibility.getId());
325                if (!roleIds.isEmpty()) {
326                            respToRoleMap.put( responsibility.getId(), getRoleService().getRoles(roleIds) );
327                }
328                    }
329        }
330                form.setResponsibilityRoles( respToRoleMap );
331        }
332
333        @Override
334        public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
335                // Repopulating the form is necessary when toggling tab states on the server side.
336                ActionForward actionForward = super.toggleTab(mapping, form, request, response);
337                populateForm( (DocumentConfigurationViewForm)form );
338                return actionForward;
339        }
340
341        /**
342         * Internal delegate class to wrap a responsibility and add an overridden flag.
343         */
344        public static class ResponsibilityForDisplay {
345
346                private Responsibility resp;
347                private boolean overridden = false;
348                
349                public ResponsibilityForDisplay( Responsibility resp, boolean overridden ) {
350                        this.resp = resp;
351                        this.overridden = overridden;
352                }
353                
354                /**
355                 * @return the resp
356                 */
357                Responsibility getResp() {
358                        return this.resp;
359                }
360                
361                public boolean isOverridden() {
362                        return this.overridden;
363                }
364
365                public void setOverridden(boolean overridden) {
366                        this.overridden = overridden;
367                }
368
369                public Map<String, String> getDetails() {
370                        return new HashMap<String, String>(this.resp.getAttributes());
371                }
372
373                public String getName() {
374                        return this.resp.getName();
375                }
376
377                public String getNamespaceCode() {
378                        return this.resp.getNamespaceCode();
379                }
380
381                public String getResponsibilityId() {
382                        return this.resp.getId();
383                }
384        }
385
386        public static class PermissionForDisplay {
387                private PermissionBo perm;
388                private boolean overridden = false;
389                
390                public PermissionForDisplay( PermissionBo perm, boolean overridden ) {
391                        this.perm = perm;
392                        this.overridden = overridden;
393                }
394                public boolean isOverridden() {
395                        return this.overridden;
396                }
397
398                public void setOverridden(boolean overridden) {
399                        this.overridden = overridden;
400                }
401                public Map<String, String> getDetails() {
402                        return this.perm.getDetails();
403                }
404                public String getName() {
405                        return this.perm.getName();
406                }
407                public String getNamespaceCode() {
408                        return this.perm.getNamespaceCode();
409                }
410        public String getId() {
411            return this.perm.getId();
412        }
413                public PermissionTemplateBo getTemplate() {
414                        return this.perm.getTemplate();
415                }
416                
417        }
418        
419        /**
420         * @return the permissionService
421         */
422        public PermissionService getPermissionService() {
423                if ( permissionService == null ) {
424                        permissionService = KimApiServiceLocator.getPermissionService();
425                }
426                return permissionService;
427        }
428
429        /**
430         * @return the roleService
431         */
432        public RoleService getRoleService() {
433                if ( roleService == null ) {
434                        roleService = KimApiServiceLocator.getRoleService();
435                }
436                return roleService;
437        }
438
439        /**
440         * @return the responsibilityService
441         */
442        public ResponsibilityService getResponsibilityService() {
443                if ( responsibilityService == null ) {
444                        responsibilityService = KimApiServiceLocator.getResponsibilityService();
445                }
446                return responsibilityService;
447        }
448
449        /**
450         * @return the documentTypeService
451         */
452        public DocumentTypeService getDocumentTypeService() {
453                if ( documentTypeService == null ) {
454                        documentTypeService = KEWServiceLocator.getDocumentTypeService();
455                }
456                return documentTypeService;
457        }
458
459        public DataDictionaryService getDataDictionaryService() {
460                if(dataDictionaryService == null){
461                        dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
462                }
463                return dataDictionaryService;
464        }
465
466        public RouteNodeService getRouteNodeService() {
467                if ( routeNodeService == null ) {
468                        routeNodeService = KEWServiceLocator.getRouteNodeService();
469                }
470                return routeNodeService;
471        }
472
473        public DocumentHelperService getDocumentHelperService() {
474                if(documentHelperService == null){
475                        documentHelperService = KNSServiceLocator.getDocumentHelperService();
476                }
477                return documentHelperService;
478        }
479
480        public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
481                if(maintenanceDocumentDictionaryService == null){
482                        maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
483                }
484                return maintenanceDocumentDictionaryService;
485        }
486        
487}