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.apache.struts.action.ActionMessages;
024import org.kuali.rice.core.api.config.property.ConfigContext;
025import org.kuali.rice.core.api.criteria.Predicate;
026import org.kuali.rice.core.api.criteria.QueryByCriteria;
027import org.kuali.rice.kew.doctype.bo.DocumentType;
028import org.kuali.rice.kew.doctype.service.DocumentTypeService;
029import org.kuali.rice.kew.engine.node.BranchPrototype;
030import org.kuali.rice.kew.engine.node.RouteNode;
031import org.kuali.rice.kew.engine.node.RouteNodeConfigParam;
032import org.kuali.rice.kew.rule.RuleBaseValues;
033import org.kuali.rice.kew.rule.bo.RuleTemplateBo;
034import org.kuali.rice.kew.rule.service.RuleServiceInternal;
035import org.kuali.rice.kew.service.KEWServiceLocator;
036import org.kuali.rice.kew.api.KewApiConstants;
037import org.kuali.rice.kew.web.KewKualiAction;
038import org.kuali.rice.kim.api.KimConstants;
039import org.kuali.rice.kim.api.group.Group;
040import org.kuali.rice.kim.api.permission.Permission;
041import org.kuali.rice.kim.api.responsibility.Responsibility;
042import org.kuali.rice.kim.api.services.KimApiServiceLocator;
043import org.kuali.rice.kns.service.DocumentHelperService;
044import org.kuali.rice.kns.service.KNSServiceLocator;
045import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
046import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
047import org.kuali.rice.krad.util.GlobalVariables;
048import org.kuali.rice.krad.util.KRADConstants;
049
050import javax.servlet.http.HttpServletRequest;
051import javax.servlet.http.HttpServletResponse;
052import java.util.ArrayList;
053import java.util.Collections;
054import java.util.Comparator;
055import java.util.HashMap;
056import java.util.Iterator;
057import java.util.List;
058
059import static org.kuali.rice.core.api.criteria.PredicateFactory.and;
060import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
061
062
063/**
064 * A Struts Action for building and interacting with the Rule Quick Links.
065 *
066 * @author Kuali Rice Team (rice.collab@kuali.org)
067 */
068public class RuleQuickLinksAction extends KewKualiAction {
069
070        private static final Logger LOG = Logger.getLogger(RuleQuickLinksAction.class);
071        
072        private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
073        private DocumentHelperService documentHelperService;
074
075    public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
076        makeLookupPathParam(mapping, request);
077        establishRequiredState(request, form);
078        return mapping.findForward("basic");
079    }
080
081    @SuppressWarnings("unchecked")
082        public ActionMessages establishRequiredState(HttpServletRequest request, ActionForm form) throws Exception {
083        RuleQuickLinksForm qlForm = (RuleQuickLinksForm) form;
084        List<DocumentType> documentTypes;
085        if (qlForm.getRootDocTypeName() != null) {
086                documentTypes = new ArrayList<DocumentType>();
087            DocumentType docType = getDocumentTypeService().findByName(qlForm.getRootDocTypeName());
088            documentTypes.add(docType);
089            request.setAttribute("renderOpened", Boolean.TRUE);
090        } else {
091                documentTypes = getDocumentTypeService().findAllCurrentRootDocuments();
092        }
093        qlForm.setDocumentTypeQuickLinksStructures(getDocumentTypeDataStructure(documentTypes));
094        int shouldDisplayCount = 0;
095        for ( DocumentTypeQuickLinksStructure dt : qlForm.getDocumentTypeQuickLinksStructures() ) {
096                if ( dt.isShouldDisplay() ) {
097                        shouldDisplayCount++;
098                }
099        }
100        if ( shouldDisplayCount == 1 ) {
101            request.setAttribute("renderOpened", Boolean.TRUE);
102        }
103        String documentTypeName = getMaintenanceDocumentDictionaryService().getDocumentTypeName(DocumentType.class);
104        try {
105            if ((documentTypeName != null) && getDocumentHelperService().getDocumentAuthorizer(documentTypeName).canInitiate(documentTypeName, GlobalVariables.getUserSession().getPerson())) {
106                qlForm.setCanInitiateDocumentTypeDocument( true );
107            }
108        } catch (Exception ex) {
109                        // just skip - and don't display links
110                LOG.error( "Unable to check initiation permission for "+ documentTypeName, ex );
111                }
112
113        return null;
114    }
115
116    public ActionForward addDelegationRule(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
117        ActionForward result = null;
118
119        String ruleTemplateId = request.getParameter("delegationRule.ruleTemplate.id");
120        String docTypeName = request.getParameter("delegationRule.documentType.name");
121        List rules = getRuleService().search(docTypeName, null, ruleTemplateId, "", null, null, Boolean.FALSE, Boolean.TRUE, new HashMap(), null);
122        
123        if (rules.size() == 1) {
124            RuleBaseValues rule = (RuleBaseValues)rules.get(0);
125            String url = ConfigContext.getCurrentContextConfig().getKEWBaseURL() +
126                "/DelegateRule.do?methodToCall=start" +
127                "&parentRuleId=" + rule.getId();
128            result = new ActionForward(url, true);
129        } else {
130                makeLookupPathParam(mapping, request);
131                result = new ActionForward(ConfigContext.getCurrentContextConfig().getKRBaseURL() + 
132                                "/lookup.do?methodToCall=start&"+ stripMethodToCall(request.getQueryString()), true);
133        }
134        
135        return result;
136    }
137
138        private List getDocumentTypeDataStructure(List rootDocuments) {
139                List documentTypeQuickLinksStructures = new ArrayList();
140                for (Iterator iter = rootDocuments.iterator(); iter.hasNext();) {
141                        DocumentTypeQuickLinksStructure quickLinkStruct =new DocumentTypeQuickLinksStructure((DocumentType) iter.next());
142                        if (! quickLinkStruct.getFlattenedNodes().isEmpty() || ! quickLinkStruct.getChildrenDocumentTypes().isEmpty()) {
143                                documentTypeQuickLinksStructures.add(quickLinkStruct);
144                        }
145
146                }
147
148                return documentTypeQuickLinksStructures;
149        }
150
151
152        /**
153     * A bean to hold a DocumentType with its flattened nodes for rendering purposes
154     * on the quick links.
155     *
156 * @author Kuali Rice Team (rice.collab@kuali.org)
157     */
158    public static class DocumentTypeQuickLinksStructure {
159        private DocumentType documentType;
160        private List<RouteNode> flattenedNodes = new ArrayList<RouteNode>();
161        private List<DocumentTypeQuickLinksStructure> childrenDocumentTypes = new ArrayList<DocumentTypeQuickLinksStructure>();
162        private List<Permission> permissions = null;
163        
164        private DocumentTypeQuickLinksStructure(DocumentType documentType) {
165                        this.documentType = documentType;
166                        if ( documentType != null ) {
167                                List<RouteNode> tempFlattenedNodes = KEWServiceLocator.getRouteNodeService()
168                                                .getFlattenedNodes( documentType, true );
169                                for ( RouteNode routeNode : tempFlattenedNodes ) {
170                                        if ( routeNode.isFlexRM() || routeNode.isRoleNode() ) {
171                                                flattenedNodes.add( new RouteNodeForDisplay( routeNode ) );
172                                        }
173                                }
174                Collections.sort(flattenedNodes,new Comparator() {
175                    public int compare(Object o1, Object o2) {
176                        return ( ((RouteNode)o1).getRouteNodeName().compareTo(((RouteNode)o2).getRouteNodeName()));
177                    }
178                });
179                                for ( Iterator<DocumentType> iter = documentType.getChildrenDocTypes().iterator(); iter.hasNext(); ) {
180                                        childrenDocumentTypes.add( new DocumentTypeQuickLinksStructure( iter.next() ) );
181                                }
182                Collections.sort(childrenDocumentTypes,new Comparator() {
183                    public int compare(Object o1, Object o2) {
184                         return ( ((DocumentTypeQuickLinksStructure)o1).documentType.getLabel().compareTo(((DocumentTypeQuickLinksStructure)o2).documentType.getLabel()));
185                    }
186                });
187                        }
188                }
189
190        public List getChildrenDocumentTypes() {
191            return childrenDocumentTypes;
192        }
193        public void setChildrenDocumentTypes(List<DocumentTypeQuickLinksStructure> childrenDocumentTypes) {
194            this.childrenDocumentTypes = childrenDocumentTypes;
195        }
196        public DocumentType getDocumentType() {
197            return documentType;
198        }
199        public void setDocumentType(DocumentType documentType) {
200            this.documentType = documentType;
201        }
202        public List getFlattenedNodes() {
203            return flattenedNodes;
204        }
205        public void setFlattenedNodes(List<RouteNode> flattenedNodes) {
206            this.flattenedNodes = flattenedNodes;
207        }
208        public boolean isShouldDisplay() {
209//                      if (flattenedNodes.isEmpty()) {
210//                              for (DocumentTypeQuickLinksStructure docType : childrenDocumentTypes) {
211//                                      if (docType.isShouldDisplay()) {
212//                                              return true;
213//                                      }
214//                              }
215//                              return false;
216//                      }
217                        return true;
218                }
219
220                public List<Permission> getPermissions() {
221                        if ( permissions == null ) {
222                Predicate p = and(
223                        equal("attributeName", "documentTypeName"),
224                        equal("active", "Y"),
225                        equal("detailCriteria",
226                                                KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME+"="+getDocumentType().getName()));
227                                permissions = KimApiServiceLocator.getPermissionService().findPermissions( QueryByCriteria.Builder.fromPredicates(p)).getResults();
228//                              sqlLogger.setLevel( Level.INFO );
229                        }
230                        return permissions;
231                }
232                
233                public boolean isHasRelatedPermissions() {
234                        return !getPermissions().isEmpty();
235                }
236                
237                public int getRelatedPermissionCount() {
238                        return getPermissions().size();
239                }
240    }
241
242    public static class RouteNodeForDisplay extends RouteNode {
243        private static final long serialVersionUID = 1L;
244                private RouteNode baseNode;
245        
246                public RouteNodeForDisplay( RouteNode baseNode ) {
247                        this.baseNode = baseNode;
248                }
249
250                public boolean equals(Object obj) {
251                        return this.baseNode.equals(obj);
252                }
253
254                public String getActivationType() {
255                        return this.baseNode.getActivationType();
256                }
257
258                public BranchPrototype getBranch() {
259                        return this.baseNode.getBranch();
260                }
261
262                public List<RouteNodeConfigParam> getConfigParams() {
263                        return this.baseNode.getConfigParams();
264                }
265
266                public String getContentFragment() {
267                        return this.baseNode.getContentFragment();
268                }
269
270                public DocumentType getDocumentType() {
271                        return this.baseNode.getDocumentType();
272                }
273
274                public String getDocumentTypeId() {
275                        return this.baseNode.getDocumentTypeId();
276                }
277                public Group getExceptionWorkgroup() {
278                        return this.baseNode.getExceptionWorkgroup();
279                }
280                public String getExceptionWorkgroupId() {
281                        return this.baseNode.getExceptionWorkgroupId();
282                }
283                public String getExceptionWorkgroupName() {
284                        return this.baseNode.getExceptionWorkgroupName();
285                }
286                public Boolean getFinalApprovalInd() {
287                        return this.baseNode.getFinalApprovalInd();
288                }
289                public Integer getLockVerNbr() {
290                        return this.baseNode.getLockVerNbr();
291                }
292                public Boolean getMandatoryRouteInd() {
293                        return this.baseNode.getMandatoryRouteInd();
294                }
295                public List<RouteNode> getNextNodes() {
296                        return this.baseNode.getNextNodes();
297                }
298                public String getNodeType() {
299                        return this.baseNode.getNodeType();
300                }
301                public List<RouteNode> getPreviousNodes() {
302                        return this.baseNode.getPreviousNodes();
303                }
304                public String getRouteMethodCode() {
305                        return this.baseNode.getRouteMethodCode();
306                }
307                public String getRouteMethodName() {
308                        return this.baseNode.getRouteMethodName();
309                }
310                public String getRouteNodeId() {
311                        return this.baseNode.getRouteNodeId();
312                }
313                public String getRouteNodeName() {
314                        return this.baseNode.getRouteNodeName();
315                }
316                public RuleTemplateBo getRuleTemplate() {
317                        return this.baseNode.getRuleTemplate();
318                }
319                public int hashCode() {
320                        return this.baseNode.hashCode();
321                }
322                public boolean isExceptionGroupDefined() {
323                        return this.baseNode.isExceptionGroupDefined();
324                }
325                public boolean isFlexRM() {
326                        return this.baseNode.isFlexRM();
327                }
328                public boolean isRoleNode() {
329                        return this.baseNode.isRoleNode();
330                }
331                public String toString() {
332                        return this.baseNode.toString();
333                }
334                
335                private List<Responsibility> responsibilities = null;
336                
337                public List<Responsibility> getResponsibilities() {
338                        if ( responsibilities == null ) {
339                                QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
340                Predicate p = and(
341                    equal("template.namespaceCode", KRADConstants.KUALI_RICE_WORKFLOW_NAMESPACE),
342                    equal("template.name", KewApiConstants.DEFAULT_RESPONSIBILITY_TEMPLATE_NAME),
343                    equal("active", "Y"),
344                    equal("attributes[documentTypeName]", getDocumentType().getName())
345                    // KULRICE-8538 -- Check the route node by looping through the results below.  If it is added
346                    // into the predicate, no rows are ever returned.
347                    // equal("attributes[routeNodeName]", getRouteNodeName())
348                );
349
350
351                builder.setPredicates(p);
352
353                List<Responsibility> possibleResponsibilities =
354                    KimApiServiceLocator.getResponsibilityService().findResponsibilities(builder.build()).getResults();
355
356                if ( !possibleResponsibilities.isEmpty() ) {
357                    for ( Responsibility resp : possibleResponsibilities ) {
358                        String routeNodeName = resp.getAttributes().get( KimConstants.AttributeConstants.ROUTE_NODE_NAME);
359                        if (StringUtils.isNotEmpty(routeNodeName) && StringUtils.equals(routeNodeName, getRouteNodeName())){
360                             responsibilities.add(resp);
361                        }
362                    }
363                } else {
364                    responsibilities = possibleResponsibilities;
365                }
366                        }
367                        return responsibilities;
368                }
369                
370                public int getResponsibilityCount() {
371                        return getResponsibilities().size();
372                }
373                
374                public boolean isHasResponsibility() {
375                        return !getResponsibilities().isEmpty();
376                }
377    }
378    
379    private void makeLookupPathParam(ActionMapping mapping, HttpServletRequest request) {
380        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + mapping.getModuleConfig().getPrefix();
381        request.setAttribute("basePath", basePath);
382    }
383
384    private String stripMethodToCall(String queryString) {
385        return queryString.replaceAll("methodToCall=addDelegationRule&", "");
386    }
387
388    private DocumentTypeService getDocumentTypeService() {
389        return KEWServiceLocator.getDocumentTypeService();
390    }
391
392    private RuleServiceInternal getRuleService() {
393        return KEWServiceLocator.getRuleService();
394    }
395    
396        public DocumentHelperService getDocumentHelperService() {
397                if(documentHelperService == null){
398                        documentHelperService = KNSServiceLocator.getDocumentHelperService();
399                }
400                return documentHelperService;
401        }
402
403        public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
404                if(maintenanceDocumentDictionaryService == null){
405                        maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
406                }
407                return maintenanceDocumentDictionaryService;
408        }
409
410        @Override
411        public ActionForward toggleTab(ActionMapping mapping, ActionForm form,
412                        HttpServletRequest request, HttpServletResponse response)
413                        throws Exception {
414                
415                establishRequiredState(request, form);
416                return super.toggleTab(mapping, form, request, response);
417        }
418
419}