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