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    }