001/**
002 * Copyright 2005-2017 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.dao.impl;
017
018import org.kuali.rice.core.api.criteria.QueryByCriteria;
019import org.kuali.rice.kew.api.KEWPropertyConstants;
020import org.kuali.rice.kew.doctype.bo.DocumentType;
021import org.kuali.rice.kew.engine.RouteHelper;
022import org.kuali.rice.kew.engine.node.NodeState;
023import org.kuali.rice.kew.engine.node.RouteNode;
024import org.kuali.rice.kew.engine.node.RouteNodeInstance;
025import org.kuali.rice.kew.engine.node.dao.RouteNodeDAO;
026import org.kuali.rice.kew.service.KEWServiceLocator;
027import org.kuali.rice.krad.data.DataObjectService;
028import org.springframework.beans.factory.annotation.Required;
029import org.springframework.dao.DataAccessException;
030import org.springframework.jdbc.core.JdbcTemplate;
031import org.springframework.jdbc.core.PreparedStatementCallback;
032import org.springframework.jdbc.core.PreparedStatementCreator;
033
034import javax.persistence.EntityManager;
035import javax.persistence.Query;
036import javax.sql.DataSource;
037import java.sql.Connection;
038import java.sql.PreparedStatement;
039import java.sql.ResultSet;
040import java.sql.SQLException;
041import java.util.ArrayList;
042import java.util.Iterator;
043import java.util.List;
044
045import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
046
047public class RouteNodeDAOJpa implements RouteNodeDAO {
048
049    private EntityManager entityManager;
050    private DataObjectService dataObjectService;
051
052    public static final String FIND_INITIAL_NODE_INSTANCES_NAME = "RouteNodeInstance.FindInitialNodeInstances";
053    public static final String FIND_INITIAL_NODE_INSTANCES_QUERY = "select d.initialRouteNodeInstances from "
054            + "DocumentRouteHeaderValue d where d.documentId = :documentId";
055
056    /**
057     * @return the entityManager
058     */
059    public EntityManager getEntityManager() {
060        return this.entityManager;
061    }
062
063    /**
064     * @param entityManager the entityManager to set
065     */
066    public void setEntityManager(EntityManager entityManager) {
067        this.entityManager = entityManager;
068    }
069
070    public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId) {
071        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
072                equal(KEWPropertyConstants.ROUTE_NODE_INSTANCE_ID, nodeInstanceId)
073        );
074
075        List<RouteNodeInstance> routeNodeInstances = getDataObjectService().findMatching(
076                RouteNodeInstance.class, queryByCriteria.build()).getResults();
077        if (routeNodeInstances != null && routeNodeInstances.size() > 0) {
078            return routeNodeInstances.get(0);
079        }
080        return null;
081    }
082
083    @SuppressWarnings("unchecked")
084    public List<RouteNodeInstance> getActiveNodeInstances(String documentId) {
085        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
086                equal(KEWPropertyConstants.DOCUMENT_ID, documentId),
087                equal(KEWPropertyConstants.ACTIVE, true)
088        );
089        return getDataObjectService().findMatching(RouteNodeInstance.class,
090                queryByCriteria.build()).getResults();
091    }
092
093    private static final String CURRENT_ROUTE_NODE_NAMES_SQL = "SELECT rn.nm, rn.typ" +
094            " FROM krew_rte_node_t rn," +
095            "      krew_rte_node_instn_t rni" +
096            " LEFT JOIN krew_rte_node_instn_lnk_t rnl" +
097            "   ON rnl.from_rte_node_instn_id = rni.rte_node_instn_id" +
098            " WHERE rn.rte_node_id = rni.rte_node_id AND" +
099            "       rni.doc_hdr_id = ? AND" +
100            "       rnl.from_rte_node_instn_id IS NULL";
101
102    @Override
103    public List<String> getCurrentRouteNodeNames(final String documentId) {
104        final DataSource dataSource = KEWServiceLocator.getDataSource();
105        JdbcTemplate template = new JdbcTemplate(dataSource);
106        List<String> names = template.execute(new PreparedStatementCreator() {
107                                                  public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
108                                                      return connection.prepareStatement(CURRENT_ROUTE_NODE_NAMES_SQL);
109                                                  }
110                                              }, new PreparedStatementCallback<List<String>>() {
111                                                  public List<String> doInPreparedStatement(
112                                                          PreparedStatement statement) throws SQLException, DataAccessException {
113                                                      List<String> routeNodeNames = new ArrayList<String>();
114                                                      statement.setString(1, documentId);
115                                                      ResultSet rs = statement.executeQuery();
116                                                      try {
117                                                          while (rs.next()) {
118                                                              String name = rs.getString("nm");
119                                                              routeNodeNames.add(name);
120                                                          }
121                                                      } finally {
122                                                          if (rs != null) {
123                                                              rs.close();
124                                                          }
125                                                      }
126                                                      return routeNodeNames;
127                                                  }
128                                              }
129        );
130        return names;
131    }
132
133    @Override
134    public List<String> getCurrentSimpleRouteNodeNames(final String documentId) {
135        final DataSource dataSource = KEWServiceLocator.getDataSource();
136        JdbcTemplate template = new JdbcTemplate(dataSource);
137        List<String> names = template.execute(new PreparedStatementCreator() {
138                                                  public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
139                                                      return connection.prepareStatement(CURRENT_ROUTE_NODE_NAMES_SQL);
140                                                  }
141                                              }, new PreparedStatementCallback<List<String>>() {
142                                                  public List<String> doInPreparedStatement(
143                                                          PreparedStatement statement) throws SQLException, DataAccessException {
144                                                      List<String> routeNodeNames = new ArrayList<String>();
145                                                      statement.setString(1, documentId);
146                                                      ResultSet rs = statement.executeQuery();
147                                                      try {
148                                                          while (rs.next()) {
149                                                              String name = rs.getString("nm");
150                                                              String type = rs.getString("typ");
151                                                              if (isSimpleNode(type)) {
152                                                                  routeNodeNames.add(name);
153                                                              }
154                                                          }
155                                                      } finally {
156                                                          if (rs != null) {
157                                                              rs.close();
158                                                          }
159                                                      }
160                                                      return routeNodeNames;
161                                                  }
162                                              }
163        );
164        return names;
165    }
166
167
168    @Override
169    public List<String> getActiveRouteNodeNames(final String documentId) {
170        final DataSource dataSource = KEWServiceLocator.getDataSource();
171        JdbcTemplate template = new JdbcTemplate(dataSource);
172        List<String> names = template.execute(
173                new PreparedStatementCreator() {
174                    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
175                        PreparedStatement statement = connection.prepareStatement(
176                                "SELECT rn.nm FROM krew_rte_node_t rn, krew_rte_node_instn_t rni WHERE rn.rte_node_id "
177                                        + "= rni.rte_node_id AND rni.doc_hdr_id = ? AND rni.actv_ind = ?");
178                        return statement;
179                    }
180                },
181                new PreparedStatementCallback<List<String>>() {
182                    public List<String> doInPreparedStatement(PreparedStatement statement) throws SQLException, DataAccessException {
183                        List<String> routeNodeNames = new ArrayList<String>();
184                        statement.setString(1, documentId);
185                        statement.setBoolean(2, Boolean.TRUE);
186                        ResultSet rs = statement.executeQuery();
187                        try {
188                            while (rs.next()) {
189                                String name = rs.getString("nm");
190                                routeNodeNames.add(name);
191                            }
192                        } finally {
193                            if (rs != null) {
194                                rs.close();
195                            }
196                        }
197                        return routeNodeNames;
198                    }
199                });
200        return names;
201    }
202
203    @Override
204    public List<String> getActiveSimpleRouteNodeNames(final String documentId) {
205        final DataSource dataSource = KEWServiceLocator.getDataSource();
206        JdbcTemplate template = new JdbcTemplate(dataSource);
207        List<String> names = template.execute(
208                new PreparedStatementCreator() {
209                    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
210                        PreparedStatement statement = connection.prepareStatement(
211                                "SELECT rn.nm, rn.typ FROM krew_rte_node_t rn, krew_rte_node_instn_t rni WHERE rn.rte_node_id "
212                                        + "= rni.rte_node_id AND rni.doc_hdr_id = ? AND rni.actv_ind = ?");
213                        return statement;
214                    }
215                },
216                new PreparedStatementCallback<List<String>>() {
217                    public List<String> doInPreparedStatement(PreparedStatement statement) throws SQLException, DataAccessException {
218                        List<String> routeNodeNames = new ArrayList<String>();
219                        statement.setString(1, documentId);
220                        statement.setBoolean(2, Boolean.TRUE);
221                        ResultSet rs = statement.executeQuery();
222                        try {
223                            while (rs.next()) {
224                                String name = rs.getString("nm");
225                                String type = rs.getString("typ");
226                                if (isSimpleNode(type)) {
227                                    routeNodeNames.add(name);
228                                }
229                            }
230                        } finally {
231                            if (rs != null) {
232                                rs.close();
233                            }
234                        }
235                        return routeNodeNames;
236                    }
237                });
238        return names;
239    }
240
241    /**
242     * This method is a bit of a hack because in order to check if a node is "simple" or not, we actually
243     * need to instantiate it's type and check the java class type. RouteHelper can do this for us but it
244     * needs an actual RouteNode object to do this work. To avoid the full cost of loading the full
245     * RouteNode object and it's associated DocumentType, we will mock out what we need here to get the job
246     * done.
247     */
248    private boolean isSimpleNode(String type) {
249        // construct a "mock" RouteNode so that we can fool the RouteHelper
250        RouteNode routeNode = new RouteNode();
251        routeNode.setNodeType(type);
252        // document type must be non-null since route helper will attempt to get the application id off of it
253        // though it's ok if the application id is null
254        routeNode.setDocumentType(new DocumentType());
255        return new RouteHelper().isSimpleNode(routeNode);
256    }
257
258    @SuppressWarnings("unchecked")
259    public List<RouteNodeInstance> getTerminalNodeInstances(String documentId) {
260        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
261                equal(KEWPropertyConstants.DOCUMENT_ID, documentId),
262                equal(KEWPropertyConstants.ACTIVE, false),
263                equal(KEWPropertyConstants.COMPLETE, true)
264        );
265
266        //FIXME: Can we do this better using just the JPQL query?
267        List<RouteNodeInstance> terminalNodes = new ArrayList<RouteNodeInstance>();
268        List<RouteNodeInstance> routeNodeInstances = getDataObjectService().
269                findMatching(RouteNodeInstance.class, queryByCriteria.build()).getResults();
270        for (RouteNodeInstance routeNodeInstance : routeNodeInstances) {
271            if (routeNodeInstance.getNextNodeInstances().isEmpty()) {
272                terminalNodes.add(routeNodeInstance);
273            }
274        }
275        return terminalNodes;
276    }
277
278    @Override
279    public List<String> getTerminalRouteNodeNames(final String documentId) {
280        final DataSource dataSource = KEWServiceLocator.getDataSource();
281        JdbcTemplate template = new JdbcTemplate(dataSource);
282        List<String> names = template.execute(new PreparedStatementCreator() {
283                                                  public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
284                                                      PreparedStatement statement = connection.prepareStatement("SELECT rn.nm" +
285                                                              "  FROM krew_rte_node_t rn," +
286                                                              "       krew_rte_node_instn_t rni" +
287                                                              "  LEFT JOIN krew_rte_node_instn_lnk_t rnl" +
288                                                              "    ON rnl.from_rte_node_instn_id = rni.rte_node_instn_id" +
289                                                              "  WHERE rn.rte_node_id = rni.rte_node_id AND" +
290                                                              "        rni.doc_hdr_id = ? AND" +
291                                                              "        rni.actv_ind = ? AND" +
292                                                              "        rni.cmplt_ind = ? AND" +
293                                                              "        rnl.from_rte_node_instn_id IS NULL");
294                                                      return statement;
295                                                  }
296                                              }, new PreparedStatementCallback<List<String>>() {
297                                                  public List<String> doInPreparedStatement(
298                                                          PreparedStatement statement) throws SQLException, DataAccessException {
299                                                      List<String> routeNodeNames = new ArrayList<String>();
300                                                      statement.setString(1, documentId);
301                                                      statement.setBoolean(2, Boolean.FALSE);
302                                                      statement.setBoolean(3, Boolean.TRUE);
303                                                      ResultSet rs = statement.executeQuery();
304                                                      try {
305                                                          while (rs.next()) {
306                                                              String name = rs.getString("nm");
307                                                              routeNodeNames.add(name);
308                                                          }
309                                                      } finally {
310                                                          if (rs != null) {
311                                                              rs.close();
312                                                          }
313                                                      }
314                                                      return routeNodeNames;
315                                                  }
316                                              }
317        );
318        return names;
319    }
320
321    public List getInitialNodeInstances(String documentId) {
322        //FIXME: Not sure this query is returning what it needs to
323        Query query = entityManager.createNamedQuery(FIND_INITIAL_NODE_INSTANCES_NAME);
324        query.setParameter(KEWPropertyConstants.DOCUMENT_ID, documentId);
325        return (List) query.getResultList();
326    }
327
328    public NodeState findNodeState(Long nodeInstanceId, String key) {
329        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
330                equal(KEWPropertyConstants.ROUTE_NODE_INSTANCE_ID, nodeInstanceId.toString()),
331                equal(KEWPropertyConstants.KEY, key)
332        );
333
334        List<NodeState> nodeStates = getDataObjectService().findMatching(
335                NodeState.class, queryByCriteria.build()).getResults();
336        if (nodeStates != null && nodeStates.size() > 0) {
337            return nodeStates.get(0);
338        }
339        return null;
340    }
341
342    public RouteNode findRouteNodeByName(String documentTypeId, String name) {
343        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
344                equal(KEWPropertyConstants.DOCUMENT_TYPE_ID, documentTypeId),
345                equal(KEWPropertyConstants.ROUTE_NODE_NAME, name)
346        );
347        List<RouteNode> routeNodes = getDataObjectService().findMatching(
348                RouteNode.class, queryByCriteria.build()).getResults();
349        if (routeNodes != null && routeNodes.size() > 0) {
350            return routeNodes.get(0);
351        }
352        return null;
353    }
354
355    public List<RouteNode> findFinalApprovalRouteNodes(String documentTypeId) {
356        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
357                equal(KEWPropertyConstants.DOCUMENT_TYPE_ID, documentTypeId),
358                equal(KEWPropertyConstants.FINAL_APPROVAL, Boolean.TRUE)
359        );
360        return getDataObjectService().findMatching(RouteNode.class, queryByCriteria.build()).getResults();
361    }
362
363    public List findProcessNodeInstances(RouteNodeInstance process) {
364        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
365                equal(KEWPropertyConstants.PROCESS_ID, process.getRouteNodeInstanceId())
366        );
367        return getDataObjectService().findMatching(RouteNodeInstance.class, queryByCriteria.build()).getResults();
368    }
369
370    public List findRouteNodeInstances(String documentId) {
371        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
372                equal(KEWPropertyConstants.DOCUMENT_ID, documentId)
373        );
374        return getDataObjectService().findMatching(RouteNodeInstance.class, queryByCriteria.build()).getResults();
375    }
376
377    public void deleteLinksToPreNodeInstances(RouteNodeInstance routeNodeInstance) {
378        List<RouteNodeInstance> preNodeInstances = routeNodeInstance.getPreviousNodeInstances();
379        for (Iterator<RouteNodeInstance> preNodeInstanceIter = preNodeInstances.iterator(); preNodeInstanceIter.hasNext(); ) {
380            RouteNodeInstance preNodeInstance = (RouteNodeInstance) preNodeInstanceIter.next();
381            List<RouteNodeInstance> nextInstances = preNodeInstance.getNextNodeInstances();
382            nextInstances.remove(routeNodeInstance);
383            getEntityManager().merge(preNodeInstance);
384        }
385    }
386
387    public void deleteRouteNodeInstancesHereAfter(RouteNodeInstance routeNodeInstance) {
388        RouteNodeInstance rnInstance = findRouteNodeInstanceById(routeNodeInstance.getRouteNodeInstanceId());
389        entityManager.remove(rnInstance);
390    }
391
392    public void deleteNodeStateById(Long nodeStateId) {
393        QueryByCriteria.Builder queryByCriteria = QueryByCriteria.Builder.create().setPredicates(
394                equal(KEWPropertyConstants.ROUTE_NODE_STATE_ID, nodeStateId)
395        );
396        List<NodeState> nodeStates = getDataObjectService().findMatching(
397                NodeState.class, queryByCriteria.build()).getResults();
398        NodeState nodeState = null;
399        if (nodeStates != null && nodeStates.size() > 0) {
400            nodeState = nodeStates.get(0);
401        }
402        getDataObjectService().delete(nodeState);
403    }
404
405    public void deleteNodeStates(List statesToBeDeleted) {
406        for (Iterator stateToBeDeletedIter = statesToBeDeleted.iterator(); stateToBeDeletedIter.hasNext(); ) {
407            Long stateId = (Long) stateToBeDeletedIter.next();
408            deleteNodeStateById(stateId);
409        }
410    }
411
412
413    public DataObjectService getDataObjectService() {
414        return dataObjectService;
415    }
416
417    @Required
418    public void setDataObjectService(DataObjectService dataObjectService) {
419        this.dataObjectService = dataObjectService;
420    }
421
422
423}