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.engine.node;
017
018import java.io.Serializable;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022
023import javax.persistence.CascadeType;
024import javax.persistence.Column;
025import javax.persistence.Entity;
026import javax.persistence.FetchType;
027import javax.persistence.GeneratedValue;
028import javax.persistence.Id;
029import javax.persistence.JoinColumn;
030import javax.persistence.JoinTable;
031import javax.persistence.ManyToMany;
032import javax.persistence.ManyToOne;
033import javax.persistence.NamedQueries;
034import javax.persistence.NamedQuery;
035import javax.persistence.OneToMany;
036import javax.persistence.OneToOne;
037import javax.persistence.Table;
038import javax.persistence.Transient;
039import javax.persistence.Version;
040
041import org.apache.commons.lang.StringUtils;
042import org.apache.log4j.Logger;
043import org.hibernate.annotations.Fetch;
044import org.hibernate.annotations.FetchMode;
045import org.hibernate.annotations.GenericGenerator;
046import org.hibernate.annotations.Parameter;
047import org.kuali.rice.core.framework.persistence.jpa.OrmUtils;
048import org.kuali.rice.kew.api.doctype.RouteNodeConfigurationParameterContract;
049import org.kuali.rice.kew.api.doctype.RouteNodeContract;
050import org.kuali.rice.kew.api.exception.ResourceUnavailableException;
051import org.kuali.rice.kew.doctype.bo.DocumentType;
052import org.kuali.rice.kew.rule.bo.RuleTemplateBo;
053import org.kuali.rice.kew.rule.service.RuleTemplateService;
054import org.kuali.rice.kew.service.KEWServiceLocator;
055import org.kuali.rice.kew.api.KewApiConstants;
056import org.kuali.rice.kew.util.Utilities;
057import org.kuali.rice.kim.api.group.Group;
058import org.kuali.rice.kim.api.services.KimApiServiceLocator;
059
060/**
061 * Represents the prototype definition of a node in the route path of {@link DocumentType}.
062 *
063 * @author Kuali Rice Team (rice.collab@kuali.org)
064 */
065@Entity
066@Table(name="KREW_RTE_NODE_T")
067//@Sequence(name="KREW_RTE_NODE_S", property="routeNodeId")
068@NamedQueries({
069        @NamedQuery(name="RouteNode.FindByRouteNodeId",query="select r from RouteNode as r where r.routeNodeId = :routeNodeId"),
070        @NamedQuery(name="RouteNode.FindRouteNodeByName", query="select r from RouteNode as r where r.routeNodeName = :routeNodeName and r.documentTypeId = :documentTypeId"),
071        @NamedQuery(name="RouteNode.FindApprovalRouteNodes", query="select r from RouteNode as r where r.documentTypeId = :documentTypeId and r.finalApprovalInd = :finalApprovalInd")
072})
073public class RouteNode implements Serializable, RouteNodeContract {    
074
075    private static final long serialVersionUID = 4891233177051752726L;
076
077    public static final String CONTENT_FRAGMENT_CFG_KEY = "contentFragment";
078    public static final String RULE_SELECTOR_CFG_KEY = "ruleSelector";
079
080    @Id
081    @GeneratedValue(generator="KREW_RTE_NODE_S")
082        @GenericGenerator(name="KREW_RTE_NODE_S",strategy="org.hibernate.id.enhanced.SequenceStyleGenerator",parameters={
083                        @Parameter(name="sequence_name",value="KREW_RTE_NODE_S"),
084                        @Parameter(name="value_column",value="id")
085        })
086        @Column(name="RTE_NODE_ID")
087        private String routeNodeId;
088    @Column(name="DOC_TYP_ID",insertable=false, updatable=false)
089        private String documentTypeId;
090    @Column(name="NM")
091        private String routeNodeName;
092    @Column(name="RTE_MTHD_NM")
093        private String routeMethodName;
094    @Column(name="FNL_APRVR_IND")
095        private Boolean finalApprovalInd;
096    @Column(name="MNDTRY_RTE_IND")
097        private Boolean mandatoryRouteInd;
098    @Column(name="GRP_ID")
099        private String exceptionWorkgroupId;
100    @Column(name="RTE_MTHD_CD")
101        private String routeMethodCode;
102    @Column(name="ACTVN_TYP")
103    private String activationType = ActivationTypeEnum.PARALLEL.getCode();
104    
105    /**
106     * The nextDocStatus property represents the value of the ApplicationDocumentStatus to be set 
107     * in the RouteHeader upon transitioning from this node.
108     */
109    @Column(name="NEXT_DOC_STAT")
110        private String nextDocStatus;
111
112    @Version
113        @Column(name="VER_NBR")
114        private Integer lockVerNbr;
115    @ManyToOne(fetch=FetchType.EAGER)
116        @JoinColumn(name="DOC_TYP_ID")
117        private DocumentType documentType;
118    @Transient
119    private String exceptionWorkgroupName;
120
121    @Transient
122    private RuleTemplateBo ruleTemplate;
123    @Column(name="TYP")
124    private String nodeType = RequestsNode.class.getName();
125    
126    //@ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST}, mappedBy="nextNodes")
127    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST}, mappedBy="nextNodes")
128    @Fetch(value = FetchMode.SELECT)
129    //@JoinTable(name = "KREW_RTE_NODE_LNK_T", joinColumns = @JoinColumn(name = "TO_RTE_NODE_ID"), inverseJoinColumns = @JoinColumn(name = "FROM_RTE_NODE_ID"))
130    private List<RouteNode> previousNodes = new ArrayList<RouteNode>();
131    //@ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
132    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
133    @Fetch(value = FetchMode.SELECT)
134    @JoinTable(name = "KREW_RTE_NODE_LNK_T", joinColumns = @JoinColumn(name = "FROM_RTE_NODE_ID"), inverseJoinColumns = @JoinColumn(name = "TO_RTE_NODE_ID"))
135    private List<RouteNode> nextNodes = new ArrayList<RouteNode>();
136    @OneToOne(fetch=FetchType.EAGER, cascade={CascadeType.PERSIST, CascadeType.MERGE})
137        @JoinColumn(name="BRCH_PROTO_ID")
138        private BranchPrototype branch;
139    @OneToMany(fetch=FetchType.EAGER,mappedBy="routeNode",cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
140    @Fetch(value = FetchMode.SELECT)
141    private List<RouteNodeConfigParam> configParams  = new ArrayList<RouteNodeConfigParam>(0);
142
143    /**
144     * Looks up a config parameter for this route node definition
145     * @param key the config param key 
146     * @return the RouteNodeConfigParam if present
147     */
148    protected RouteNodeConfigParam getConfigParam(String key) {
149        Map<String, RouteNodeConfigParam> configParamMap = Utilities.getKeyValueCollectionAsLookupTable(configParams);
150        return configParamMap.get(key);
151    }
152
153    /**
154     * Sets a config parameter for this route node definition.  If the key already exists
155     * the existing RouteNodeConfigParam is modified, otherwise a new one is created
156     * @param key the key of the parameter to set
157     * @param value the value to set
158     */
159    protected void setConfigParam(String key, String value) {
160        Map<String, RouteNodeConfigParam> configParamMap = Utilities.getKeyValueCollectionAsLookupTable(configParams);
161        RouteNodeConfigParam cfCfgParam = configParamMap.get(key);
162        if (cfCfgParam == null) {
163            cfCfgParam = new RouteNodeConfigParam(this, key, value);
164            configParams.add(cfCfgParam);
165        } else {
166            cfCfgParam.setValue(value);
167        }
168    }
169
170    public List<RouteNodeConfigParam> getConfigParams() {
171        return configParams;
172    }
173
174    public void setConfigParams(List<RouteNodeConfigParam> configParams) {
175        this.configParams = configParams;
176    }
177
178    /**
179     * @return the RouteNodeConfigParam value under the 'contentFragment'  key
180     */
181    public String getContentFragment() {
182        RouteNodeConfigParam cfCfgParam = getConfigParam(CONTENT_FRAGMENT_CFG_KEY);
183        if (cfCfgParam == null) return null;
184        return cfCfgParam.getValue();
185    }
186
187    /**
188     * @param contentFragment the content fragment of the node, which will be set as a RouteNodeConfigParam under the 'contentFragment' key
189     */
190    public void setContentFragment(String contentFragment) {
191        setConfigParam(CONTENT_FRAGMENT_CFG_KEY, contentFragment);
192    }
193
194    public String getActivationType() {
195        return activationType;
196    }
197
198    public void setActivationType(String activationType) {
199        /* Cleanse the input.
200         * This is surely not the best way to validate the activation types;
201         * it would probably be better to use typesafe enums accross the board
202         * but that would probably entail refactoring large swaths of code, not
203         * to mention reconfiguring OJB (can typesafe enums be used?) and dealing
204         * with serialization compatibility issues (if any).
205         * So instead, let's just be sure to fail-fast.
206         */
207        ActivationTypeEnum at = ActivationTypeEnum.lookupCode(activationType);
208        this.activationType = at.getCode();
209    }
210
211    public Group getExceptionWorkgroup() {
212        if (!StringUtils.isBlank(exceptionWorkgroupId)) {
213                return KimApiServiceLocator.getGroupService().getGroup(exceptionWorkgroupId);
214        }
215        return null;
216    }
217    
218    public boolean isExceptionGroupDefined() {
219        return getExceptionWorkgroupId() != null;
220    }
221
222    public String getExceptionWorkgroupId() {
223        return exceptionWorkgroupId;
224    }
225
226    public void setExceptionWorkgroupId(String workgroupId) {
227        this.exceptionWorkgroupId = workgroupId;
228    }
229
230    public void setFinalApprovalInd(Boolean finalApprovalInd) {
231        this.finalApprovalInd = finalApprovalInd;
232    }
233
234    public void setMandatoryRouteInd(Boolean mandatoryRouteInd) {
235        this.mandatoryRouteInd = mandatoryRouteInd;
236    }
237
238    public String getRouteMethodName() {
239        return routeMethodName;
240    }
241
242    public void setRouteMethodName(String routeMethodName) {
243        this.routeMethodName = routeMethodName;
244    }
245
246    public String getDocumentTypeId() {
247        return documentTypeId;
248    }
249
250    public void setDocumentTypeId(String documentTypeId) {
251        this.documentTypeId = documentTypeId;
252    }
253
254    public String getRouteNodeId() {
255        return routeNodeId;
256    }
257
258    public void setRouteNodeId(String routeNodeId) {
259        this.routeNodeId = routeNodeId;
260    }
261
262    public String getRouteNodeName() {
263        return routeNodeName;
264    }
265
266    public void setRouteNodeName(String routeLevelName) {
267        this.routeNodeName = routeLevelName;
268    }
269
270    public DocumentType getDocumentType() {
271        return documentType;
272    }
273
274    public void setDocumentType(DocumentType documentType) {
275        this.documentType = documentType;
276    }
277
278    public String getRouteMethodCode() {
279        return routeMethodCode;
280    }
281
282    public void setRouteMethodCode(String routeMethodCode) {
283        this.routeMethodCode = routeMethodCode;
284    }
285
286        /**
287         * @param nextDocStatus the nextDocStatus to set
288         */
289        public void setNextDocStatus(String nextDocStatus) {
290                this.nextDocStatus = nextDocStatus;
291        }
292
293        /**
294         * @return the nextDocStatus
295         */
296        public String getNextDocStatus() {
297                return nextDocStatus;
298        }
299        
300    public String getExceptionWorkgroupName() {
301        Group exceptionGroup = getExceptionWorkgroup();
302        if (exceptionWorkgroupName == null || exceptionWorkgroupName.equals("")) {
303            if (exceptionGroup != null) {
304                return exceptionGroup.getName();
305            }
306        }
307        return exceptionWorkgroupName;
308    }
309
310    public void setExceptionWorkgroupName(String exceptionWorkgroupName) {
311        this.exceptionWorkgroupName = exceptionWorkgroupName;
312    }
313
314    public Integer getLockVerNbr() {
315        return lockVerNbr;
316    }
317
318    public void setLockVerNbr(Integer lockVerNbr) {
319        this.lockVerNbr = lockVerNbr;
320    }
321
322    public boolean isFlexRM() {
323        return routeMethodCode != null && routeMethodCode.equals(KewApiConstants.ROUTE_LEVEL_FLEX_RM);
324    }
325
326    public boolean isRulesEngineNode() {
327        return StringUtils.equals(routeMethodCode, KewApiConstants.ROUTE_LEVEL_RULES_ENGINE);
328    }
329
330    public boolean isPeopleFlowNode() {
331        return StringUtils.equals(routeMethodCode, KewApiConstants.ROUTE_LEVEL_PEOPLE_FLOW);
332    }
333    
334    public boolean isRoleNode() {
335        try {
336                return nodeType != null && NodeType.fromNode(this).isTypeOf(NodeType.ROLE);
337        } catch( ResourceUnavailableException ex ) {
338                Logger.getLogger( RouteNode.class ).info( "isRoleNode(): Unable to determine node type: " + ex.getMessage() );
339                return false;
340        }
341    }
342
343    public Boolean getFinalApprovalInd() {
344        return finalApprovalInd;
345    }
346
347    public Boolean getMandatoryRouteInd() {
348        return mandatoryRouteInd;
349    }
350
351    public void addNextNode(RouteNode nextNode) {
352        getNextNodes().add(nextNode);
353        nextNode.getPreviousNodes().add(this);
354    }
355
356    public List<RouteNode> getNextNodes() {
357        return nextNodes;
358    }
359
360    public void setNextNodes(List<RouteNode> nextNodes) {
361        this.nextNodes = nextNodes;
362    }
363
364    public List<RouteNode> getPreviousNodes() {
365        return previousNodes;
366    }
367
368    public void setPreviousNodes(List<RouteNode> parentNodes) {
369        this.previousNodes = parentNodes;
370    }
371
372    public RuleTemplateBo getRuleTemplate() {
373        if (ruleTemplate == null) {
374            RuleTemplateService ruleTemplateService = (RuleTemplateService) KEWServiceLocator.getService(KEWServiceLocator.RULE_TEMPLATE_SERVICE);
375            ruleTemplate = ruleTemplateService.findByRuleTemplateName(getRouteMethodName());
376        }
377        return ruleTemplate;
378    }
379
380    public String getNodeType() {
381        return nodeType;
382    }
383
384    public void setNodeType(String nodeType) {
385        this.nodeType = nodeType;
386    }
387
388    public BranchPrototype getBranch() {
389        return branch;
390    }
391
392    public void setBranch(BranchPrototype branch) {
393        this.branch = branch;
394    }
395
396        //@PrePersist
397        public void beforeInsert(){
398                OrmUtils.populateAutoIncValue(this, KEWServiceLocator.getEntityManagerFactory().createEntityManager());
399        }
400        
401        /**
402         * This overridden method ...
403         * 
404         * @see java.lang.Object#toString()
405         */
406        @Override
407        public String toString() {
408                return "RouteNode[routeNodeName="+routeNodeName+", nodeType="+nodeType+", activationType="+activationType+"]";
409        }
410
411    @Override
412    public Long getVersionNumber() {
413        if (lockVerNbr == null) {
414            return null;
415        }
416        return Long.valueOf(lockVerNbr.longValue());
417    }
418
419    @Override
420    public String getId() {
421        if (routeNodeId == null) {
422            return null;
423        }
424        return routeNodeId.toString();
425    }
426
427    @Override
428    public String getName() {
429        return getRouteNodeName();
430    }
431
432    @Override
433    public boolean isFinalApproval() {
434        if (finalApprovalInd == null) {
435            return false;
436        }
437        return finalApprovalInd.booleanValue();
438    }
439
440    @Override
441    public boolean isMandatory() {
442        if (mandatoryRouteInd == null) {
443            return false;
444        }
445        return mandatoryRouteInd.booleanValue();
446    }
447
448    @Override
449    public String getExceptionGroupId() {
450        return exceptionWorkgroupId;
451    }
452
453    @Override
454    public String getType() {
455        return nodeType;
456    }
457
458    @Override
459    public String getBranchName() {
460        if (branch == null) {
461            return null;
462        }
463        return branch.getName();
464    }
465
466    @Override
467    public String getNextDocumentStatus() {
468        return nextDocStatus;
469    }
470
471    @Override
472    public List<? extends RouteNodeConfigurationParameterContract> getConfigurationParameters() {
473        return configParams;
474    }
475
476    @Override
477    public List<String> getPreviousNodeIds() {
478        List<String> previousNodeIds = new ArrayList<String>();
479        if (previousNodes != null) {
480            for (RouteNode previousNode : previousNodes) {
481                previousNodeIds.add(previousNode.getRouteNodeId().toString());
482            }
483        }
484        return previousNodeIds;
485    }
486
487    @Override
488    public List<String> getNextNodeIds() {
489        List<String> nextNodeIds = new ArrayList<String>();
490        if (nextNodeIds != null) {
491            for (RouteNode nextNode : nextNodes) {
492                nextNodeIds.add(nextNode.getRouteNodeId().toString());
493            }
494        }
495        return nextNodeIds;
496    }
497        
498        
499
500}