001    /**
002     * Copyright 2005-2014 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.engine.node;
017    
018    import java.io.Serializable;
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.Map;
022    
023    import javax.persistence.CascadeType;
024    import javax.persistence.Column;
025    import javax.persistence.Entity;
026    import javax.persistence.FetchType;
027    import javax.persistence.GeneratedValue;
028    import javax.persistence.Id;
029    import javax.persistence.JoinColumn;
030    import javax.persistence.JoinTable;
031    import javax.persistence.ManyToMany;
032    import javax.persistence.ManyToOne;
033    import javax.persistence.NamedQueries;
034    import javax.persistence.NamedQuery;
035    import javax.persistence.OneToMany;
036    import javax.persistence.OneToOne;
037    import javax.persistence.Table;
038    import javax.persistence.Transient;
039    import javax.persistence.Version;
040    
041    import org.apache.commons.lang.StringUtils;
042    import org.apache.log4j.Logger;
043    import org.hibernate.annotations.Fetch;
044    import org.hibernate.annotations.FetchMode;
045    import org.hibernate.annotations.GenericGenerator;
046    import org.hibernate.annotations.Parameter;
047    import org.kuali.rice.core.framework.persistence.jpa.OrmUtils;
048    import org.kuali.rice.kew.api.doctype.RouteNodeConfigurationParameterContract;
049    import org.kuali.rice.kew.api.doctype.RouteNodeContract;
050    import org.kuali.rice.kew.api.exception.ResourceUnavailableException;
051    import org.kuali.rice.kew.doctype.bo.DocumentType;
052    import org.kuali.rice.kew.rule.bo.RuleTemplateBo;
053    import org.kuali.rice.kew.rule.service.RuleTemplateService;
054    import org.kuali.rice.kew.service.KEWServiceLocator;
055    import org.kuali.rice.kew.api.KewApiConstants;
056    import org.kuali.rice.kew.util.Utilities;
057    import org.kuali.rice.kim.api.group.Group;
058    import 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    })
073    public 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    }