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 java.util.Collection;
019import java.util.HashSet;
020import java.util.Set;
021
022import org.apache.commons.lang.StringUtils;
023import org.apache.log4j.Logger;
024import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
025import org.kuali.rice.core.api.exception.RiceRuntimeException;
026import org.kuali.rice.kcb.bo.Message;
027import org.kuali.rice.kcb.bo.MessageDelivery;
028import org.kuali.rice.kcb.bo.RecipientDelivererConfig;
029import org.kuali.rice.kcb.api.message.MessageDTO;
030import org.kuali.rice.kcb.api.exception.MessageDeliveryException;
031import org.kuali.rice.kcb.api.exception.MessageDismissalException;
032import org.kuali.rice.kcb.quartz.MessageProcessingJob;
033import org.kuali.rice.kcb.service.MessageDeliveryService;
034import org.kuali.rice.kcb.service.MessageService;
035import org.kuali.rice.kcb.api.service.MessagingService;
036import org.kuali.rice.kcb.service.RecipientPreferenceService;
037import org.kuali.rice.ksb.service.KSBServiceLocator;
038import org.quartz.JobDataMap;
039import org.quartz.Scheduler;
040import org.quartz.SchedulerException;
041import org.quartz.SimpleTrigger;
042import org.quartz.impl.triggers.SimpleTriggerImpl;
043import org.springframework.beans.factory.annotation.Required;
044import org.springframework.transaction.support.TransactionSynchronizationAdapter;
045import org.springframework.transaction.support.TransactionSynchronizationManager;
046
047/**
048 * MessagingService implementation 
049 * 
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 */
052public class MessagingServiceImpl implements MessagingService {
053    private static final Logger LOG = Logger.getLogger(MessagingServiceImpl.class);
054
055    private MessageService messageService;
056    private MessageDeliveryService messageDeliveryService;
057    private RecipientPreferenceService recipientPrefs;
058    private String jobName;
059    private String jobGroup;
060
061    /**
062     * Whether to perform the processing  synchronously
063     */
064    private boolean synchronous;
065    
066    /**
067     * Sets the name of the target job to run to process messages
068     * @param jobName the name of the target job to run to process messages
069     */
070    public void setJobName(String jobName) {
071        this.jobName = jobName;
072    }
073
074    /**
075     * Sets the group of the target job to run to process messages
076     * @param jobGroup Sets the group of the target job to run to process messages
077     */
078    public void setJobGroup(String jobGroup) {
079        this.jobGroup = jobGroup;
080    }
081
082    /**
083     * Sets the MessageService
084     * @param messageService the MessageService
085     */
086    @Required
087    public void setMessageService(MessageService messageService) {
088        this.messageService = messageService;
089    }
090
091    /**
092     * Sets the MessageDeliveryService
093     * @param messageDeliveryService the MessageDeliveryService
094     */
095    @Required
096    public void setMessageDeliveryService(MessageDeliveryService messageDeliveryService) {
097        this.messageDeliveryService = messageDeliveryService;
098    }
099
100    /**
101     * Sets whether to perform the processing synchronously
102     * @param sync whether to perform the processing synchronously
103     */
104    public void setSynchronous(boolean sync) {
105        LOG.debug("Setting synchronous messaging to: " + sync);
106        this.synchronous = sync;
107    }
108
109    /**
110     * Sets the RecipientPreferencesService
111     * @param prefs the RecipientPreferenceService
112     */
113    @Required
114    public void setRecipientPreferenceService(RecipientPreferenceService prefs) {
115        this.recipientPrefs = prefs;
116    }
117
118    /**
119     * @see org.kuali.rice.kcb.service.MessagingService#deliver(org.kuali.rice.kcb.dto.MessageDTO)
120     */
121    @Override
122    public Long deliver(MessageDTO message) throws MessageDeliveryException {
123        if (message == null) {
124            throw new RiceIllegalArgumentException("message is null");
125        }
126
127        Collection<String> delivererTypes = getDelivererTypesForUserAndChannel(message.getRecipient(), message.getChannel());
128        LOG.debug("Deliverer types for " + message.getRecipient() + "/" + message.getChannel() + ": " + delivererTypes.size());
129
130        if (delivererTypes.isEmpty()) {
131            // no deliverers configured? just skipp it
132            LOG.debug("No deliverers are configured for " + message.getRecipient() + "/" + message.getChannel());
133            return null;
134        }
135
136        Message m = new Message();
137        m.setTitle(message.getTitle());
138        m.setDeliveryType(message.getDeliveryType());
139        m.setChannel(message.getChannel());
140        m.setRecipient(message.getRecipient());
141        m.setContentType(message.getContentType());
142        m.setUrl(message.getUrl());
143        m.setContent(message.getContent());
144        m.setOriginId(message.getOriginId());
145
146        LOG.debug("saving message: " +m);
147        m = messageService.saveMessage(m);
148
149        for (String type: delivererTypes) {
150            
151            MessageDelivery delivery = new MessageDelivery();
152            delivery.setDelivererTypeName(type);
153            delivery.setMessage(m);
154
155//            MessageDeliverer deliverer = delivererRegistry.getDeliverer(delivery);
156//            if (deliverer != null) {
157//                deliverer.deliverMessage(delivery);
158//            }
159        
160            LOG.debug("saving messagedelivery: " +delivery);
161            messageDeliveryService.saveMessageDelivery(delivery);
162        }
163
164        LOG.debug("queuing job");
165        queueJob(MessageProcessingJob.Mode.DELIVER, m.getId(), null, null);
166
167        LOG.debug("returning");
168        return m.getId();
169    }
170
171    /**
172     * @see org.kuali.rice.kcb.service.MessagingService#remove(long, java.lang.String, java.lang.String)
173     */
174    @Override
175    public void remove(long messageId, String user, String cause) throws MessageDismissalException {
176        /*if (StringUtils.isBlank(messageId)) {
177            throw new RiceIllegalArgumentException("message is null");
178        } if we switch to String id*/
179
180        if (StringUtils.isBlank(user)) {
181            throw new RiceIllegalArgumentException("user is null");
182        }
183
184        if (StringUtils.isBlank(cause)) {
185            throw new RiceIllegalArgumentException("cause is null");
186        }
187
188        Message m = messageService.getMessage(Long.valueOf(messageId));
189        if (m == null) {
190            throw new MessageDismissalException("No such message: " + messageId);
191        }
192
193        remove (m, user, cause);
194    }
195
196    /**
197     * @see org.kuali.rice.kcb.service.MessagingService#removeByOriginId(java.lang.String, java.lang.String, java.lang.String)
198     */
199    @Override
200    public Long removeByOriginId(String originId, String user, String cause) throws MessageDismissalException {
201        if (StringUtils.isBlank(originId)) {
202            throw new RiceIllegalArgumentException("originId is null");
203        }
204
205        Message m = messageService.getMessageByOriginId(originId);
206        if (m == null) {
207            return null; 
208            //throw new MessageDismissalException("No such message with origin id: " + originId);
209        }
210        remove(m, user, cause);
211        return m.getId();
212    }
213
214    private void remove(Message message, String user, String cause) {
215        queueJob(MessageProcessingJob.Mode.REMOVE, message.getId(), user, cause);
216    }
217
218    /**
219     * Determines what delivery endpoints the user has configured
220     * @param userRecipientId the user
221     * @return a Set of NotificationConstants.MESSAGE_DELIVERY_TYPES
222     */
223    private Collection<String> getDelivererTypesForUserAndChannel(String userRecipientId, String channel) {
224        Set<String> deliveryTypes = new HashSet<String>(1);
225        
226        // manually add the default one since they don't have an option on this one
227        //deliveryTypes.add(NotificationConstants.MESSAGE_DELIVERY_TYPES.DEFAULT_MESSAGE_DELIVERY_TYPE);
228        
229        //now look for what they've configured for themselves
230        Collection<RecipientDelivererConfig> deliverers = recipientPrefs.getDeliverersForRecipientAndChannel(userRecipientId, channel);
231        
232        for (RecipientDelivererConfig cfg: deliverers) {
233            deliveryTypes.add(cfg.getDelivererName());
234        }
235        //return GlobalNotificationServiceLocator.getInstance().getKENAPIService().getDeliverersForRecipientAndChannel(userRecipientId, channel);
236
237        return deliveryTypes;
238    }
239
240    private void queueJob(MessageProcessingJob.Mode mode, long messageId, String user, String cause) {
241        // queue up the processing job after the transaction has committed
242        LOG.debug("registering synchronization");
243
244        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
245                throw new RiceRuntimeException("transaction syncronization is not active " +
246                                "(!TransactionSynchronizationManager.isSynchronizationActive())");
247        } else if (!TransactionSynchronizationManager.isActualTransactionActive()) {
248                throw new RiceRuntimeException("actual transaction is not active " +
249                                "(!TransactionSynchronizationManager.isActualTransactionActive())");
250        }
251
252        TransactionSynchronizationManager.registerSynchronization(new QueueProcessingJobSynchronization(
253            jobName,
254            jobGroup,
255            mode,
256            messageId,
257            user,
258            cause,
259            synchronous
260        ));
261    }
262    
263    public static class QueueProcessingJobSynchronization extends TransactionSynchronizationAdapter {
264        private static final Logger LOG = Logger.getLogger(QueueProcessingJobSynchronization.class);
265        private final String jobName;
266        private final String jobGroup;
267        private final MessageProcessingJob.Mode mode;
268        private final long messageId;
269        private final String user;
270        private final String cause;
271        private final boolean synchronous;
272
273        private QueueProcessingJobSynchronization(String jobName, String jobGroup, MessageProcessingJob.Mode mode, long messageId, String user, String cause, boolean synchronous) {
274            this.jobName = jobName;
275            this.jobGroup = jobGroup;
276            this.mode = mode;
277            this.messageId = messageId;
278            this.user = user;
279            this.cause = cause;
280            this.synchronous = synchronous;
281        }
282
283        /*
284        @Override
285        public void beforeCommit(boolean readOnly) {
286            super.beforeCommit(readOnly);
287        }*/
288
289        @Override
290        public void afterCommit() {
291            scheduleJob();
292        }
293        /*@Override
294        public void afterCompletion(int status) {
295            if (STATUS_COMMITTED == status) {
296                scheduleJob();
297            } else {
298                LOG.error("Status is not committed.  Not scheduling message processing job.");
299            }
300        }*/
301
302        private void scheduleJob() {
303            LOG.debug("Queueing processing job");
304            try {
305                Scheduler scheduler = KSBServiceLocator.getScheduler();
306                if (synchronous) {
307                    LOG.debug("Invoking job synchronously in Thread " + Thread.currentThread());
308                    MessageProcessingJob job = new MessageProcessingJob(messageId, mode, user, cause);
309                    job.run();
310                } else {
311                    String uniqueTriggerName = jobName + "-Trigger-" + System.currentTimeMillis() + Math.random();
312                    SimpleTriggerImpl trigger = new SimpleTriggerImpl(uniqueTriggerName, jobGroup + "-Trigger");
313                    LOG.debug("Scheduling trigger: " + trigger);
314
315                    JobDataMap data = new JobDataMap();
316                    data.put("mode", mode.name());
317                    data.put("user", user);
318                    data.put("cause", cause);
319                    data.put("messageId", messageId);
320
321                    trigger.setJobName(jobName);
322                    trigger.setJobGroup(jobGroup);
323                    trigger.setJobDataMap(data);
324                    scheduler.scheduleJob(trigger);
325                }
326            } catch (SchedulerException se) {
327                throw new RuntimeException(se);
328            }
329        }
330    }
331}