001/**
002 * Copyright 2005-2015 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.builder.ToStringBuilder;
019import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState;
020import org.kuali.rice.kew.doctype.bo.DocumentType;
021import org.kuali.rice.kew.engine.node.dao.impl.RouteNodeDAOJpa;
022import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
023import org.kuali.rice.kew.service.KEWServiceLocator;
024import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
025
026import javax.persistence.CascadeType;
027import javax.persistence.Column;
028import javax.persistence.Entity;
029import javax.persistence.GeneratedValue;
030import javax.persistence.Id;
031import javax.persistence.JoinColumn;
032import javax.persistence.JoinTable;
033import javax.persistence.ManyToMany;
034import javax.persistence.ManyToOne;
035import javax.persistence.NamedQueries;
036import javax.persistence.NamedQuery;
037import javax.persistence.OneToMany;
038import javax.persistence.Table;
039import javax.persistence.Version;
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.Iterator;
043import java.util.List;
044import java.util.Map;
045
046/**
047 * Represents a materialized instance of a {@link RouteNode} definition on a {@link DocumentRouteHeaderValue}.  Node instances
048 * are generated by the engine using the {@link RouteNode} as a prototype and connected as a 
049 * Directed Acyclic Graph.
050 *
051 * @author Kuali Rice Team (rice.collab@kuali.org)
052 */
053@Entity
054@Table(name="KREW_RTE_NODE_INSTN_T")
055@NamedQueries({
056        @NamedQuery(name= RouteNodeDAOJpa.FIND_INITIAL_NODE_INSTANCES_NAME,
057            query=RouteNodeDAOJpa.FIND_INITIAL_NODE_INSTANCES_QUERY)
058})
059public class RouteNodeInstance implements Serializable {
060    
061        private static final long serialVersionUID = 7183670062805580420L;
062        
063        @Id
064        @GeneratedValue(generator="KREW_RTE_NODE_S")
065    @PortableSequenceGenerator(name="KREW_RTE_NODE_S")
066        @Column(name="RTE_NODE_INSTN_ID")
067        private String routeNodeInstanceId;
068
069    @Column(name="DOC_HDR_ID")
070        private String documentId;
071
072    @ManyToOne(cascade = CascadeType.ALL)
073        @JoinColumn(name="BRCH_ID")
074        private Branch branch;
075
076    @ManyToOne
077    @JoinColumn(name="RTE_NODE_ID", nullable = false)
078    private RouteNode routeNode;
079
080    @Column(name="ACTV_IND")
081    private boolean active = false;
082
083    @Column(name="CMPLT_IND")
084    private boolean complete = false;
085
086    @Column(name="INIT_IND")
087    private boolean initial = true;
088
089    @ManyToOne(cascade = CascadeType.ALL)
090        @JoinColumn(name="PROC_RTE_NODE_INSTN_ID")
091        private RouteNodeInstance process;
092
093    @ManyToMany(cascade = CascadeType.ALL)
094    @JoinTable(name = "KREW_RTE_NODE_INSTN_LNK_T",
095            joinColumns = @JoinColumn(name = "FROM_RTE_NODE_INSTN_ID"),
096            inverseJoinColumns = @JoinColumn(name = "TO_RTE_NODE_INSTN_ID"))
097    private List<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>();
098    
099    @ManyToMany(mappedBy = "nextNodeInstances")
100    @JoinTable(name = "KREW_RTE_NODE_INSTN_LNK_T",
101            joinColumns = @JoinColumn(name = "TO_RTE_NODE_INSTN_ID", updatable = false, insertable = false),
102            inverseJoinColumns = @JoinColumn(name = "FROM_RTE_NODE_INSTN_ID", updatable = false, insertable = false))
103    private List<RouteNodeInstance> previousNodeInstances = new ArrayList<RouteNodeInstance>();
104
105    @OneToMany(cascade = CascadeType.ALL, mappedBy = "nodeInstance", orphanRemoval = true)
106    private List<NodeState> state = new ArrayList<NodeState>();
107        
108    @Version
109        @Column(name="VER_NBR")
110        private Integer lockVerNbr;
111
112    public boolean isActive() {
113        return active;
114    }
115    public void setActive(boolean active) {
116        this.active = active;
117    }
118    
119    public boolean isComplete() {
120        return complete;
121    }
122    public void setComplete(boolean complete) {
123        this.complete = complete;
124    }
125    public Branch getBranch() {
126        return branch;
127    }
128    public void setBranch(Branch branch) {
129        this.branch = branch;
130    }
131    public RouteNode getRouteNode() {
132        return routeNode;
133    }
134    public void setRouteNode(RouteNode node) {
135        this.routeNode = node;
136    }
137    public String getRouteNodeInstanceId() {
138        return routeNodeInstanceId;
139    }
140    public void setRouteNodeInstanceId(String routeNodeInstanceId) {
141        this.routeNodeInstanceId = routeNodeInstanceId;
142    }
143    public String getDocumentId() {
144        return documentId;
145    }
146    public void setDocumentId(String documentId) {
147        this.documentId = documentId;
148    }
149    public List<RouteNodeInstance> getNextNodeInstances() {
150        return nextNodeInstances;
151    }
152    public RouteNodeInstance getNextNodeInstance(int index) {
153        while (getNextNodeInstances().size() <= index) {
154                nextNodeInstances.add(new RouteNodeInstance());
155        }
156        return getNextNodeInstances().get(index);
157    }
158    public void setNextNodeInstances(List<RouteNodeInstance> nextNodeInstances) {
159        this.nextNodeInstances = nextNodeInstances;
160    }
161    public List<RouteNodeInstance> getPreviousNodeInstances() {
162        return previousNodeInstances;
163    }
164    public RouteNodeInstance getPreviousNodeInstance(int index) {
165        while (previousNodeInstances.size() <= index) {
166                previousNodeInstances.add(new RouteNodeInstance());
167        }
168        return (RouteNodeInstance) getPreviousNodeInstances().get(index);
169    }
170    public void setPreviousNodeInstances(List<RouteNodeInstance> previousNodeInstances) {
171        this.previousNodeInstances = previousNodeInstances;
172    }
173    public boolean isInitial() {
174        return initial;
175    }
176    public void setInitial(boolean initial) {
177        this.initial = initial;
178    }
179    public List<NodeState> getState() {
180        return state;
181    }
182    public void setState(List<NodeState> state) {
183        this.state.clear();
184        this.state.addAll(state);
185        //this.state = state;
186    }
187    public RouteNodeInstance getProcess() {
188                return process;
189        }
190        public void setProcess(RouteNodeInstance process) {
191                this.process = process;
192        }
193        public Integer getLockVerNbr() {
194        return lockVerNbr;
195    }
196    public void setLockVerNbr(Integer lockVerNbr) {
197        this.lockVerNbr = lockVerNbr;
198    }
199
200    public String getRouteNodeId() {
201        if (getRouteNode() == null) {
202            return null;
203        }
204        return getRouteNode().getRouteNodeId();
205    }
206
207    public NodeState getNodeState(String key) {
208        for (Iterator iter = getState().iterator(); iter.hasNext();) {
209            NodeState nodeState = (NodeState) iter.next();
210            if (nodeState.getKey().equals(key)) {
211                return nodeState;
212            }
213        }
214        return null;
215    }
216    
217    public void addNodeState(NodeState state) {
218        this.state.add(state);
219        state.setNodeInstance(this);
220    }
221    
222    public void removeNodeState(String key) {
223        for (Iterator iter = getState().iterator(); iter.hasNext();) {
224            NodeState nodeState = (NodeState) iter.next();
225            if (nodeState.getKey().equals(key)) {
226                iter.remove();
227                break;
228            }
229        }
230    }
231    
232    public void addNextNodeInstance(RouteNodeInstance nextNodeInstance) {
233        nextNodeInstances.add(nextNodeInstance);
234        nextNodeInstance.getPreviousNodeInstances().add(this);
235    }
236    
237    public void removeNextNodeInstance(RouteNodeInstance nextNodeInstance) {
238        nextNodeInstances.remove(nextNodeInstance);
239        nextNodeInstance.getPreviousNodeInstances().remove(this);
240    }
241    
242    public void clearNextNodeInstances() {
243        for (Iterator iterator = nextNodeInstances.iterator(); iterator.hasNext();) {
244            RouteNodeInstance nextNodeInstance = (RouteNodeInstance) iterator.next();
245            iterator.remove();
246            nextNodeInstance.getPreviousNodeInstances().remove(this);
247        }
248    }
249    
250    public String getName() {
251        return (getRouteNode() == null ? null : getRouteNode().getRouteNodeName());
252    }
253    
254    public boolean isInProcess() {
255        return getProcess() != null;
256    }
257    
258    public DocumentType getDocumentType() {
259        return KEWServiceLocator.getDocumentTypeService().findByDocumentId(getDocumentId());
260    }
261    
262    /*
263     * methods used to display route node instances' data on documentoperation.jsp
264     */
265    
266    public NodeState getNodeStateByIndex(int index){
267        while (state.size() <= index) {
268            state.add(new NodeState());
269        }
270        return getState().get(index);
271    }   
272
273    public void populateState(List<NodeState> state) {
274        this.state.addAll(state);
275    }
276
277    public RouteNodeInstance deepCopy(Map<Object, Object> visited) {
278        if (visited.containsKey(this)) {
279            return (RouteNodeInstance)visited.get(this);
280        }
281        RouteNodeInstance copy = new RouteNodeInstance();
282        visited.put(this, copy);
283        copy.routeNodeInstanceId = routeNodeInstanceId;
284        copy.documentId = documentId;
285        copy.active = active;
286        copy.complete = complete;
287        copy.initial = initial;
288        copy.lockVerNbr = lockVerNbr;
289        // no need to deep copy route node because it's static configuration
290        copy.routeNode = routeNode;
291        if (branch != null) {
292            copy.branch = branch.deepCopy(visited);
293        }
294        if (process != null) {
295            copy.process = process.deepCopy(visited);
296        }
297        if (nextNodeInstances != null) {
298            List<RouteNodeInstance> copies = new ArrayList<RouteNodeInstance>();
299            for (RouteNodeInstance nextNodeInstance : nextNodeInstances) {
300                copies.add(nextNodeInstance.deepCopy(visited));
301            }
302            copy.nextNodeInstances = copies;
303        }
304        if (previousNodeInstances != null) {
305            List<RouteNodeInstance> copies = new ArrayList<RouteNodeInstance>();
306            for (RouteNodeInstance previousNodeInstance : previousNodeInstances) {
307                copies.add(previousNodeInstance.deepCopy(visited));
308            }
309            copy.previousNodeInstances = copies;
310        }
311        if (state != null) {
312            List<NodeState> copies = new ArrayList<NodeState>();
313            for (NodeState aState : state) {
314                copies.add(aState.deepCopy(visited));
315            }
316            copy.state = copies;
317        }
318        return copy;
319    }
320    
321    public String toString() {
322        return new ToStringBuilder(this)
323            .append("routeNodeInstanceId", routeNodeInstanceId)
324            .append("documentId", documentId)
325            .append("branch", branch == null ? null : branch.getBranchId())
326            .append("routeNode", routeNode == null ? null : routeNode.getRouteNodeName() + " " + routeNode.getRouteNodeId())
327            .append("active", active)
328            .append("complete", complete)
329            .append("initial", initial)
330            .append("process", process)
331            .append("state", state == null ? null : state.size())
332            .toString();
333    }
334
335        public static org.kuali.rice.kew.api.document.node.RouteNodeInstance to(RouteNodeInstance routeNodeInstance) {
336                if (routeNodeInstance == null) {
337                        return null;
338                }
339                org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder builder = org.kuali.rice.kew.api.document.node
340                .RouteNodeInstance.Builder.create();
341                builder.setActive(routeNodeInstance.isActive());
342                builder.setBranchId(routeNodeInstance.getBranch().getBranchId());
343                builder.setComplete(routeNodeInstance.isComplete());
344                builder.setDocumentId(routeNodeInstance.getDocumentId());
345                builder.setId(routeNodeInstance.getRouteNodeInstanceId());
346                builder.setInitial(routeNodeInstance.isInitial());
347                builder.setName(routeNodeInstance.getName());
348                if (routeNodeInstance.getProcess() != null) {
349                        builder.setProcessId(routeNodeInstance.getProcess().getRouteNodeInstanceId());
350                }
351                builder.setRouteNodeId(routeNodeInstance.getRouteNode().getRouteNodeId());
352                List<RouteNodeInstanceState.Builder> states = new ArrayList<RouteNodeInstanceState.Builder>();
353                for (NodeState stateBo : routeNodeInstance.getState()) {
354                        RouteNodeInstanceState.Builder stateBuilder = RouteNodeInstanceState.Builder.create();
355                        stateBuilder.setId(stateBo.getStateId());
356                        stateBuilder.setKey(stateBo.getKey());
357                        stateBuilder.setValue(stateBo.getValue());
358                        states.add(stateBuilder);
359                }
360                builder.setState(states);
361
362        List<org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder> nextNodes = new ArrayList<org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder>();
363        if (routeNodeInstance.getNextNodeInstances() != null) {
364            for (RouteNodeInstance next : routeNodeInstance.getNextNodeInstances()) {
365                // KULRICE-8152 - During load testing, sometimes routeNodeInstance.getNextNodeInstances() returns an
366                // arraylist with size = 1 but all elements are null, which causes a "contract was null" error when the
367                // create is called.  This check to see if next is not null prevents the error from occurring.
368                if (next != null) {
369                    // will this make things blow up?
370                    nextNodes.add(org.kuali.rice.kew.api.document.node.RouteNodeInstance.Builder.create(RouteNodeInstance.to(next)));
371                }
372            }
373        }
374        builder.setNextNodeInstances(nextNodes);
375
376                return builder.build();
377
378
379
380        }
381    
382}
383