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.kcb.service.impl;
017
018import org.apache.log4j.Logger;
019import org.kuali.rice.core.api.criteria.Predicate;
020import org.kuali.rice.core.api.criteria.QueryByCriteria;
021import org.kuali.rice.kcb.bo.Message;
022import org.kuali.rice.kcb.bo.MessageDelivery;
023import org.kuali.rice.kcb.bo.MessageDeliveryStatus;
024import org.kuali.rice.kcb.service.MessageDeliveryService;
025import org.kuali.rice.krad.data.DataObjectService;
026
027import java.sql.Timestamp;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.List;
031
032import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
033
034/**
035 * MessageDeliveryService implementation 
036 * 
037 * @author Kuali Rice Team (rice.collab@kuali.org)
038 */
039public class MessageDeliveryServiceImpl implements MessageDeliveryService {
040    private static final Logger LOG = Logger.getLogger(MessageDeliveryServiceImpl.class);
041
042    private DataObjectService dataObjectService;
043
044    /**
045     * Number of processing attempts to make.  {@link MessageDelivery}s with this number or more of attempts
046     * will not be selected for further processing.
047     */
048    private int maxProcessAttempts;
049
050    /**
051     * Sets the max processing attempts
052     * @param maxProcessAttempts the max delivery attempts
053     */
054    public void setMaxProcessAttempts(int maxProcessAttempts) {
055        this.maxProcessAttempts = maxProcessAttempts;
056    }
057
058    /**
059     * @see org.kuali.rice.kcb.service.MessageDeliveryService#saveMessageDelivery(org.kuali.rice.kcb.bo.MessageDelivery)
060     */
061    public MessageDelivery saveMessageDelivery(MessageDelivery delivery) {
062        return dataObjectService.save(delivery);
063    }
064
065    /**
066     * @see org.kuali.rice.kcb.service.MessageDeliveryService#deleteMessageDelivery(MessageDelivery)
067     */
068    public void deleteMessageDelivery(MessageDelivery messageDelivery) {
069        dataObjectService.delete(messageDelivery);
070    }
071
072    /**
073     * @see org.kuali.rice.kcb.service.MessageDeliveryService#getAllMessageDeliveries()
074     */
075    public Collection<MessageDelivery> getAllMessageDeliveries() {
076        return dataObjectService.findMatching(MessageDelivery.class, QueryByCriteria.Builder.create().build()).getResults();
077    }
078
079    /**
080     * @see org.kuali.rice.kcb.service.MessageDeliveryService#getMessageDelivery(java.lang.Long)
081     */
082    public MessageDelivery getMessageDelivery(Long id) {
083        return dataObjectService.find(MessageDelivery.class, id);
084    }
085
086    /**
087     * @see org.kuali.rice.kcb.service.MessageDeliveryService#getMessageDeliveryByDelivererSystemId(java.lang.Long)
088     */
089    public MessageDelivery getMessageDeliveryByDelivererSystemId(Long id) {
090        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create();
091
092        criteria.setPredicates(equal(MessageDelivery.SYSTEMID_FIELD, id));
093        List<MessageDelivery> results = dataObjectService.findMatching(MessageDelivery.class, criteria.build()).getResults();
094
095        if (results.isEmpty()) {
096            return null;
097        }
098        if (results.size() > 1) {
099            throw new RuntimeException("More than one message delivery found with the following delivery system id: " + id);
100        }
101        return results.get(0);
102    }
103
104    /**
105     * @see org.kuali.rice.kcb.service.MessageDeliveryService#getMessageDeliveries(org.kuali.rice.kcb.bo.Message)
106     */
107    public Collection<MessageDelivery> getMessageDeliveries(Message message) {
108        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create();
109        criteria.setPredicates(equal(MessageDelivery.MESSAGEID_FIELD, message.getId()));
110
111        return dataObjectService.findMatching(MessageDelivery.class, criteria.build()).getResults();
112    }
113
114    /* This method is responsible for atomically finding messagedeliveries, marking them as taken
115     * and returning them to the caller for processing.
116     * NOTE: it is important that this method execute in a SEPARATE dedicated transaction; either the caller should
117     * NOT be wrapped by Spring declarative transaction and this service should be wrapped (which is the case), or
118     * the caller should arrange to invoke this from within a newly created transaction).
119     */
120    public Collection<MessageDelivery> lockAndTakeMessageDeliveries(MessageDeliveryStatus[] statuses) {
121        return lockAndTakeMessageDeliveries(null, statuses);
122    }
123    public Collection<MessageDelivery> lockAndTakeMessageDeliveries(Long messageId, MessageDeliveryStatus[] statuses) {
124        LOG.debug("========>> ENTERING LockAndTakeMessageDeliveries: " + Thread.currentThread());
125        // DO WITHIN TRANSACTION: get all untaken messagedeliveries, and mark as "taken" so no other thread/job takes them
126        // need to think about durability of work list
127
128        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create();
129        List<Predicate> predicates = new ArrayList<Predicate>();
130
131        predicates.add(isNull(MessageDelivery.LOCKED_DATE));
132        if (messageId != null) {
133            predicates.add(equal(MessageDelivery.MESSAGEID_FIELD + ".id", messageId));
134        }
135        predicates.add(lessThan(MessageDelivery.PROCESS_COUNT, maxProcessAttempts));
136
137        Collection<String> statusCollection = new ArrayList<String>(statuses.length);
138        for (MessageDeliveryStatus status: statuses) {
139            statusCollection.add(status.name());
140        }
141        predicates.add(in(MessageDelivery.DELIVERY_STATUS, statusCollection));
142        criteria.setPredicates(predicates.toArray(new Predicate[predicates.size()]));
143        List<MessageDelivery> messageDeliveries = dataObjectService.findMatching(MessageDelivery.class, criteria.build()).getResults();
144        List<MessageDelivery> lockedMsgDels = new ArrayList<MessageDelivery>();
145
146        // mark messageDeliveries as taken
147        for (MessageDelivery delivery: messageDeliveries) {
148            LOG.debug("Took: " + delivery);
149            delivery.setLockedDate(new Timestamp(System.currentTimeMillis()));
150            delivery = dataObjectService.save(delivery);
151            lockedMsgDels.add(delivery);
152        }
153
154        LOG.debug("<<=======  LEAVING LockAndTakeMessageDeliveries: " + Thread.currentThread());
155        return lockedMsgDels;
156    }
157
158    /**
159     * Sets the data object service.
160     * @param dataObjectService service to persist data to the datasource
161     */
162    public void setDataObjectService(DataObjectService dataObjectService) {
163        this.dataObjectService = dataObjectService;
164    }
165}