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.impl.stuck;
017
018import org.springframework.beans.factory.annotation.Required;
019
020import javax.persistence.EntityManager;
021import javax.persistence.Query;
022import javax.persistence.TypedQuery;
023import javax.persistence.criteria.CriteriaBuilder;
024import javax.persistence.criteria.CriteriaQuery;
025import javax.persistence.criteria.ParameterExpression;
026import javax.persistence.criteria.Root;
027import java.sql.Timestamp;
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Set;
033import java.util.stream.Collectors;
034
035/**
036 * @author Eric Westfall
037 */
038public class StuckDocumentDaoJpa implements StuckDocumentDao {
039
040    private static final String NEW_STUCK_DOCUMENT_ACTION_ITEM_SQL =
041            "select DH.DOC_HDR_ID from KREW_DOC_HDR_T DH " +
042                    "left outer join KREW_ACTN_ITM_T AI on DH.DOC_HDR_ID=AI.DOC_HDR_ID " +
043                    "left outer join (select DOC_HDR_ID, STATUS, START_DT, END_DT from KREW_STUCK_DOC_INCIDENT_T where (DOC_HDR_ID, START_DT) in (select DOC_HDR_ID, MAX(START_DT) from KREW_STUCK_DOC_INCIDENT_T group by DOC_HDR_ID)) SD " +
044                    "on DH.DOC_HDR_ID=SD.DOC_HDR_ID " +
045                    "where DH.DOC_HDR_STAT_CD='R' AND AI.DOC_HDR_ID IS NULL and " +
046                    "(SD.DOC_HDR_ID IS NULL OR SD.STATUS='FIXED' OR (SD.STATUS='FAILED' AND DH.STAT_MDFN_DT > SD.END_DT))";
047
048    private static final String NEW_STUCK_DOCUMENT_ACTION_REQUEST_SQL =
049            "select DH.DOC_HDR_ID from KREW_DOC_HDR_T DH " +
050                    "left outer join (select DOC_HDR_ID, STAT_CD from KREW_ACTN_RQST_T AR where AR.STAT_CD='A') AR on DH.DOC_HDR_ID=AR.DOC_HDR_ID " +
051                    "left outer join (select DOC_HDR_ID, STATUS, START_DT, END_DT from KREW_STUCK_DOC_INCIDENT_T where (DOC_HDR_ID, START_DT) in (select DOC_HDR_ID, MAX(START_DT) from KREW_STUCK_DOC_INCIDENT_T group by DOC_HDR_ID)) as SD " +
052                    "on DH.DOC_HDR_ID=SD.DOC_HDR_ID " +
053                    "where DH.DOC_HDR_STAT_CD='R' and AR.DOC_HDR_ID IS NULL and " +
054                    "(SD.DOC_HDR_ID IS NULL OR SD.STATUS='FIXED' OR (SD.STATUS='FAILED' AND DH.STAT_MDFN_DT > SD.END_DT))";
055
056    private static final String ALL_STUCK_DOCUMENT_ACTION_ITEM_SQL =
057            "select DH.DOC_HDR_ID, DH.CRTE_DT, DT.LBL from KREW_DOC_HDR_T DH " +
058                    "left outer join KREW_ACTN_ITM_T AI on DH.DOC_HDR_ID=AI.DOC_HDR_ID " +
059                    "join KREW_DOC_TYP_T DT on DH.DOC_TYP_ID=DT.DOC_TYP_ID " +
060                    "where DH.DOC_HDR_STAT_CD='R' AND AI.DOC_HDR_ID IS NULL";
061
062    private static final String ALL_STUCK_DOCUMENT_ACTION_REQUEST_SQL =
063            "select DH.DOC_HDR_ID, DH.CRTE_DT, DT.LBL from KREW_DOC_HDR_T DH " +
064                    "left outer join KREW_ACTN_RQST_T AR on DH.DOC_HDR_ID=AR.DOC_HDR_ID AND AR.STAT_CD = 'A' " +
065                    "join KREW_DOC_TYP_T DT on DH.DOC_TYP_ID=DT.DOC_TYP_ID " +
066                    "where DH.DOC_HDR_STAT_CD='R' AND AR.DOC_HDR_ID IS NULL";
067
068    private static final String IS_STUCK_DOCUMENT_ACTION_ITEM_SQL =
069            ALL_STUCK_DOCUMENT_ACTION_ITEM_SQL + " AND DH.DOC_HDR_ID = ?";
070
071    private static final String IS_STUCK_DOCUMENT_ACTION_REQUEST_SQL =
072            ALL_STUCK_DOCUMENT_ACTION_REQUEST_SQL + " AND DH.DOC_HDR_ID = ?";
073
074    static final String FIX_ATTEMPTS_FOR_INCIDENT_NAME = "StuckDocumentFixAttempt.FixAttemptsForIncident";
075    static final String FIX_ATTEMPTS_FOR_INCIDENT_QUERY = "select fa from StuckDocumentFixAttempt fa where fa.stuckDocumentIncidentId = :stuckDocumentIncidentId";
076
077
078
079    private EntityManager entityManager;
080
081    @Override
082    public List<String> findAllStuckDocumentIds() {
083        return findAllStuckDocuments().stream().map(StuckDocument::getDocumentId).collect(Collectors.toList());
084    }
085
086    @Override
087    public List<StuckDocument> findAllStuckDocuments() {
088        List<Object[]> stuckDocumentResults = new ArrayList<>();
089        stuckDocumentResults.addAll(entityManager.createNativeQuery(ALL_STUCK_DOCUMENT_ACTION_ITEM_SQL).getResultList());
090        stuckDocumentResults.addAll(entityManager.createNativeQuery(ALL_STUCK_DOCUMENT_ACTION_REQUEST_SQL).getResultList());
091        List<StuckDocument> unfilteredStuckDocuments = stuckDocumentResults.stream().map(result -> new StuckDocument((String)result[0], (String)result[2], ((Timestamp)result[1]).toLocalDateTime())).collect(Collectors.toList());
092        return filterDuplicateStuckDocuments(unfilteredStuckDocuments);
093    }
094
095    private List<StuckDocument> filterDuplicateStuckDocuments(List<StuckDocument> unfilteredStuckDocuments) {
096        Set<String> processedDocumentIds = new HashSet<>();
097        return unfilteredStuckDocuments.stream().filter(stuckDocument -> {
098            if (processedDocumentIds.contains(stuckDocument.getDocumentId())) {
099                return false;
100            }
101            processedDocumentIds.add(stuckDocument.getDocumentId());
102            return true;
103        }).collect(Collectors.toList());
104    }
105
106    @Override
107    public StuckDocumentIncident findIncident(String stuckDocumentIncidentId) {
108        return entityManager.find(StuckDocumentIncident.class, stuckDocumentIncidentId);
109    }
110
111    @Override
112    public StuckDocumentIncident saveIncident(StuckDocumentIncident incident) {
113        return entityManager.merge(incident);
114    }
115
116    public void deleteIncident(StuckDocumentIncident incident) {
117        entityManager.remove(incident);
118    }
119
120    @Override
121    public List<StuckDocumentIncident> findAllIncidents(int maxIncidents) {
122        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
123        CriteriaQuery<StuckDocumentIncident> q = cb.createQuery(StuckDocumentIncident.class);
124        Root<StuckDocumentIncident> incident = q.from(StuckDocumentIncident.class);
125        q.select(incident).orderBy(cb.desc(incident.get("startDate")));
126        TypedQuery<StuckDocumentIncident> query = getEntityManager().createQuery(q);
127        query.setMaxResults(maxIncidents);
128        return new ArrayList<>(query.getResultList());
129    }
130
131    @Override
132    public List<StuckDocumentIncident> findIncidentsByStatus(int maxIncidents, StuckDocumentIncident.Status status) {
133        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
134        CriteriaQuery<StuckDocumentIncident> q = cb.createQuery(StuckDocumentIncident.class);
135        Root<StuckDocumentIncident> incident = q.from(StuckDocumentIncident.class);
136        q.select(incident).orderBy(cb.desc(incident.get("startDate")));
137        ParameterExpression<StuckDocumentIncident.Status> statusParameter = cb.parameter(StuckDocumentIncident.Status.class);
138        q.where(cb.equal(incident.get("status"), statusParameter));
139        TypedQuery<StuckDocumentIncident> query = getEntityManager().createQuery(q);
140        query.setParameter(statusParameter, status);
141        query.setMaxResults(maxIncidents);
142        return new ArrayList<>(query.getResultList());
143    }
144
145    @Override
146    public List<StuckDocumentFixAttempt> findAllFixAttempts(String stuckDocumentIncidentId) {
147        TypedQuery<StuckDocumentFixAttempt> query = getEntityManager().createNamedQuery(FIX_ATTEMPTS_FOR_INCIDENT_NAME, StuckDocumentFixAttempt.class);
148        query.setParameter("stuckDocumentIncidentId", stuckDocumentIncidentId);
149        return new ArrayList<>(query.getResultList());
150    }
151
152    @Override
153    public StuckDocumentFixAttempt saveFixAttempt(StuckDocumentFixAttempt auditEntry) {
154        return entityManager.merge(auditEntry);
155    }
156
157    @Override
158    public List<String> identifyNewStuckDocuments() {
159        Set<String> documentIds = new HashSet<>();
160        documentIds.addAll(entityManager.createNativeQuery(NEW_STUCK_DOCUMENT_ACTION_ITEM_SQL).getResultList());
161        documentIds.addAll(entityManager.createNativeQuery(NEW_STUCK_DOCUMENT_ACTION_REQUEST_SQL).getResultList());
162        return Collections.unmodifiableList(new ArrayList<>(documentIds));
163    }
164
165    public boolean isStuck(String documentId) {
166        Query aiQuery = entityManager.createNativeQuery(IS_STUCK_DOCUMENT_ACTION_ITEM_SQL);
167        Query arQuery = entityManager.createNativeQuery(IS_STUCK_DOCUMENT_ACTION_REQUEST_SQL);
168        aiQuery.setParameter(1, documentId);
169        arQuery.setParameter(1, documentId);
170        return !aiQuery.getResultList().isEmpty() || !arQuery.getResultList().isEmpty();
171    }
172
173    @Override
174    public List<StuckDocumentIncident> identifyStillStuckDocuments(List<String> incidentIds) {
175        return incidentIds.stream().map(this::findIncident).filter(incident -> isStuck(incident.getDocumentId())).collect(Collectors.toList());
176    }
177
178    public EntityManager getEntityManager() {
179        return entityManager;
180    }
181
182    @Required
183    public void setEntityManager(EntityManager entityManager) {
184        this.entityManager = entityManager;
185    }
186
187}