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}