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.routeheader.dao.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.config.property.ConfigContext;
020import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
021import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
022import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValueContent;
023import org.kuali.rice.kew.routeheader.dao.DocumentRouteHeaderDAO;
024import org.kuali.rice.krad.data.DataObjectService;
025import org.kuali.rice.krad.data.platform.MaxValueIncrementerFactory;
026import org.springframework.beans.factory.annotation.Required;
027import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
028
029import javax.persistence.EntityManager;
030import javax.persistence.LockModeType;
031import javax.persistence.Query;
032import javax.persistence.TypedQuery;
033import javax.sql.DataSource;
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Map;
039import java.util.Set;
040
041public class DocumentRouteHeaderDAOJpa implements DocumentRouteHeaderDAO {
042
043    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentRouteHeaderDAOJpa.class);
044
045    private static final String LOCK_TIMEOUT_HINT = "javax.persistence.lock.timeout";
046    private static final Long DEFAULT_LOCK_TIMEOUT_SECONDS = Long.valueOf(60 * 60); // default to 1 hour
047
048    public static final String GET_APP_DOC_ID_NAME = "DocumentRouteHeaderValue.GetAppDocId";
049    public static final String GET_APP_DOC_ID_QUERY = "SELECT d.appDocId from DocumentRouteHeaderValue "
050            + "as d where d.documentId = :documentId";
051    public static final String GET_APP_DOC_STATUS_NAME = "DocumentRouteHeaderValue.GetAppDocStatus";
052    public static final String GET_APP_DOC_STATUS_QUERY = "SELECT d.appDocStatus from "
053            + "DocumentRouteHeaderValue as d where d.documentId = :documentId";
054    public static final String GET_DOCUMENT_HEADERS_NAME = "DocumentRouteHeaderValue.GetDocumentHeaders";
055    public static final String GET_DOCUMENT_HEADERS_QUERY = "SELECT d from DocumentRouteHeaderValue "
056            + "as d where d.documentId IN :documentIds";
057    public static final String GET_DOCUMENT_STATUS_NAME = "DocumentRouteHeaderValue.GetDocumentStatus";
058    public static final String GET_DOCUMENT_STATUS_QUERY = "SELECT d.docRouteStatus from "
059            + "DocumentRouteHeaderValue as d where d.documentId = :documentId";
060    public static final String GET_DOCUMENT_ID_BY_DOC_TYPE_APP_ID_NAME =
061            "DocumentRouteHeaderValue.GetDocumentIdByDocTypeAndAppId";
062    public static final String GET_DOCUMENT_ID_BY_DOC_TYPE_APP_ID_QUERY = "SELECT "
063            + "DISTINCT(DH.documentId) FROM DocumentRouteHeaderValue DH, DocumentType DT "
064            + "WHERE DH.appDocId = :appDocId AND DH.documentTypeId = DT.documentTypeId  AND DT.name = :name";
065
066        private EntityManager entityManager;
067    private DataSource dataSource;
068
069    private DataObjectService dataObjectService;
070
071    @Override
072    public Collection<DocumentRouteHeaderValue> findRouteHeaders(Collection<String> documentIds){
073        if (documentIds.isEmpty()) {
074            return new ArrayList<DocumentRouteHeaderValue>();
075        }
076        TypedQuery<DocumentRouteHeaderValue> query = getEntityManager().
077                createNamedQuery(GET_DOCUMENT_HEADERS_NAME, DocumentRouteHeaderValue.class);
078        query.setParameter("documentIds",documentIds);
079        return query.getResultList();
080    }
081
082    @Override
083    public Collection<DocumentRouteHeaderValue> findRouteHeaders(Collection<String> documentIds, boolean clearCache){
084        Collection<DocumentRouteHeaderValue> documentRouteHeaderValues = findRouteHeaders(documentIds);
085        if(clearCache){
086            for(DocumentRouteHeaderValue drhv : documentRouteHeaderValues){
087                getEntityManager().refresh(drhv);
088            }
089
090        }
091        return documentRouteHeaderValues;
092    }
093
094    @Override
095    public void lockRouteHeader(final String documentId) {
096        // passing a hint here on the lock timeout, this will really only work on Oracle since it supports "wait"
097        // on a SELECT ... FOR UPDATE but other databases don't
098        //
099        // one random additional piece of trivia to note, if the timeout comes back non-zero, then EclipseLink just
100        // ingores it when sending it to MySQL and issues a plain SELECT ... FOR UPDATE. However if it cames back as 0,
101        // it will try to issue a SELECT ... FOR UPDATE NOWAIT even thouh MySQL doesn't support it. This, of course,
102        // triggers an exception from the MySQL database
103        //
104        // the moral of the story? don't ever set the timeout to zero, at least not until EclipseLink fixes that bug
105        Map<String, Object> options = new HashMap<String, Object>();
106        options.put(LOCK_TIMEOUT_HINT, getTimeoutMilliseconds());
107        getEntityManager().find(DocumentRouteHeaderValue.class, documentId, LockModeType.PESSIMISTIC_WRITE, options);
108    }
109
110    protected Long getTimeoutMilliseconds() {
111        Long secondsToWait = DEFAULT_LOCK_TIMEOUT_SECONDS;
112        String timeoutValue = ConfigContext.getCurrentContextConfig().getDocumentLockTimeout();
113        if (timeoutValue != null) {
114            try {
115                secondsToWait = Long.parseLong(timeoutValue);
116            } catch (NumberFormatException e) {
117                LOG.warn("Failed to parse document lock timeout as it was not a valid number: " + timeoutValue);
118            }
119        }
120        return secondsToWait * 1000;
121    }
122
123    @Override
124    public DocumentRouteHeaderValue findRouteHeader(String documentId, boolean clearCache) {
125        DocumentRouteHeaderValue dv = getDataObjectService().find(DocumentRouteHeaderValue.class,documentId);
126        if(clearCache){
127            getEntityManager().refresh(dv);
128        }
129        return dv;
130    }
131
132    @Override
133    public String getNextDocumentId(){
134        DataFieldMaxValueIncrementer incrementer = MaxValueIncrementerFactory.getIncrementer(
135                                        getDataSource(), "KREW_DOC_HDR_S");
136        return incrementer.nextStringValue();
137    }
138
139    public Collection<String> findPendingByResponsibilityIds(Set<String> responsibilityIds) {
140        List<String> documentIds = new ArrayList<String>();
141        if (responsibilityIds.isEmpty()) {
142            return documentIds;
143        }
144        TypedQuery<String> query =
145                getEntityManager().createNamedQuery("ActionRequestValue.FindPendingByResponsibilityIds", String.class);
146        query.setParameter("respIds", responsibilityIds);
147        return query.getResultList();
148    }
149
150    public void clearRouteHeaderSearchValues(String documentId) {
151
152        Query query = getEntityManager().
153                createNamedQuery("SearchableAttributeValue.FindSearchableAttributesByDocumentId");
154        query.setParameter("documentId",documentId);
155        List<SearchableAttributeValue> searchableAttributeValues =
156                (List<SearchableAttributeValue>)query.getResultList();
157        for(SearchableAttributeValue sa : searchableAttributeValues){
158            getDataObjectService().delete(sa);
159        }
160    }
161
162    @Override
163    public Collection<SearchableAttributeValue> findSearchableAttributeValues(String documentId) {
164        Query query = getEntityManager().createNamedQuery(
165                "SearchableAttributeValue.FindSearchableAttributesByDocumentId");
166        query.setParameter("documentId",documentId);
167        return query.getResultList();
168    }
169
170    @Override
171    public DocumentRouteHeaderValueContent getContent(String documentId) {
172        DocumentRouteHeaderValueContent content = null;
173        Query query = getEntityManager().createNamedQuery("DocumentRouteHeaderValueContent.FindByDocumentId");
174        query.setParameter("documentId",documentId);
175        if(query.getResultList() != null && !query.getResultList().isEmpty()) {
176          content = (DocumentRouteHeaderValueContent)query.getResultList().get(0);
177        }
178        return content;
179    }
180
181    @Override
182    public boolean hasSearchableAttributeValue(String documentId, String searchableAttributeKey, String searchableAttributeValue) {
183        Query query = getEntityManager().createNamedQuery("SearchableAttributeValue.HasSearchableAttributeValue");
184        query.setParameter("documentId",documentId);
185        query.setParameter("searchableAttributeKey",searchableAttributeKey);
186
187        if(query.getResultList() != null && !query.getResultList().isEmpty()){
188            for(Object ob : query.getResultList()){
189                SearchableAttributeValue sav = (SearchableAttributeValue)ob;
190                if (StringUtils.equals(sav.getSearchableAttributeDisplayValue(), searchableAttributeValue)) {
191                    return true;
192                }
193            }
194        }
195        return false;
196    }
197
198    public String getDocumentStatus(String documentId) {
199        String status = null;
200
201        Query query = getEntityManager().createNamedQuery(GET_DOCUMENT_STATUS_NAME);
202        query.setParameter("documentId",documentId);
203        if(query.getResultList() != null && !query.getResultList().isEmpty()){
204            status = (String)query.getResultList().get(0);
205        }
206        return status;
207    }
208
209    @Override
210    public void save(SearchableAttributeValue searchableAttribute) {
211        getDataObjectService().save(searchableAttribute);
212    }
213
214    public String getAppDocId(String documentId) {
215        TypedQuery<String> query = getEntityManager().createNamedQuery(GET_APP_DOC_ID_NAME,String.class
216        );
217        query.setParameter("documentId",documentId);
218
219        String applicationDocId = null;
220        if(query.getResultList() != null && !query.getResultList().isEmpty()){
221            applicationDocId = query.getResultList().get(0);
222        }
223        return applicationDocId;
224
225    }
226
227    public String getApplicationIdByDocumentId(String documentId) {
228        if (documentId == null) {
229            throw new IllegalArgumentException("Encountered a null document ID.");
230        }
231
232        String applicationId = null;
233
234        TypedQuery<String> query = getEntityManager().createNamedQuery(
235                "DocumentType.GetAppIdByDocumentId",String.class);
236        query.setParameter("documentId",documentId);
237        if(query.getResultList() != null && !query.getResultList().isEmpty()){
238             applicationId = query.getResultList().get(0);
239        }
240        return applicationId;
241
242    }
243
244    public String getAppDocStatus(String documentId) {
245        String applicationDocumentStatus = null;
246
247        TypedQuery<String> query = getEntityManager().createNamedQuery(GET_APP_DOC_STATUS_NAME,String.class);
248        query.setParameter("documentId",documentId);
249        if(query.getResultList() != null && !query.getResultList().isEmpty()){
250            applicationDocumentStatus = query.getResultList().get(0);
251        }
252        return applicationDocumentStatus;
253    }
254
255    public Collection findByDocTypeAndAppId(String documentTypeName,
256            String appId) {
257        TypedQuery<String> query = getEntityManager().createNamedQuery(GET_DOCUMENT_ID_BY_DOC_TYPE_APP_ID_NAME,
258                String.class);
259        query.setParameter("appDocId",appId);
260        query.setParameter("name",documentTypeName);
261        return query.getResultList();
262    }
263
264    /**
265     * @return the entityManager
266     */
267    public EntityManager getEntityManager() {
268        return this.entityManager;
269    }
270
271    /**
272     * @param entityManager the entityManager to set
273     */
274    public void setEntityManager(EntityManager entityManager) {
275        this.entityManager = entityManager;
276    }
277
278    public DataSource getDataSource() {
279        return dataSource;
280    }
281
282    public void setDataSource(DataSource dataSource) {
283        this.dataSource = dataSource;
284    }
285
286    public DataObjectService getDataObjectService() {
287        return dataObjectService;
288    }
289
290    @Required
291    public void setDataObjectService(DataObjectService dataObjectService) {
292        this.dataObjectService = dataObjectService;
293    }
294
295}