/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.notifications; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.json.JSONException; import com.liferay.portal.kernel.json.JSONFactoryUtil; import com.liferay.portal.kernel.json.JSONObject; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.CompanyConstants; import com.liferay.portal.kernel.model.UserNotificationEvent; import com.liferay.portal.kernel.notifications.BaseChannelImpl; import com.liferay.portal.kernel.notifications.ChannelException; import com.liferay.portal.kernel.notifications.NotificationEvent; import com.liferay.portal.kernel.notifications.NotificationEventComparator; import com.liferay.portal.kernel.notifications.NotificationEventFactoryUtil; import com.liferay.portal.kernel.service.UserNotificationEventLocalServiceUtil; import com.liferay.portal.util.PropsValues; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * @author Edward Han * @author Brian Wing Shun Chan * @author Jonathan Lee */ public class ChannelImpl extends BaseChannelImpl { public ChannelImpl() { this(CompanyConstants.SYSTEM, 0); } public ChannelImpl(long companyId, long usedId) { super(companyId, usedId); } @Override public void confirmDelivery(Collection<String> notificationEventUuids) throws ChannelException { confirmDelivery(notificationEventUuids, false); } @Override public void confirmDelivery( Collection<String> notificationEventUuids, boolean archive) throws ChannelException { lock.lock(); try { if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED) { if (archive) { UserNotificationEventLocalServiceUtil. updateUserNotificationEvents( notificationEventUuids, getCompanyId(), archive); } else { UserNotificationEventLocalServiceUtil. deleteUserNotificationEvents( notificationEventUuids, getCompanyId()); } } for (String notificationEventUuid : notificationEventUuids) { Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); unconfirmedNotificationEvents.remove(notificationEventUuid); } } catch (Exception e) { throw new ChannelException( "Unable to confirm delivery for user " + getUserId(), e); } finally { lock.unlock(); } } @Override public void confirmDelivery(String notificationEventUuid) throws ChannelException { confirmDelivery(notificationEventUuid, false); } @Override public void confirmDelivery(String notificationEventUuid, boolean archive) throws ChannelException { lock.lock(); try { if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED) { if (archive) { UserNotificationEventLocalServiceUtil. updateUserNotificationEvent( notificationEventUuid, getCompanyId(), archive); } else { UserNotificationEventLocalServiceUtil. deleteUserNotificationEvent( notificationEventUuid, getCompanyId()); } } Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); unconfirmedNotificationEvents.remove(notificationEventUuid); } catch (Exception e) { throw new ChannelException( "Unable to confirm delivery for " + notificationEventUuid, e); } finally { lock.unlock(); } } @Override public void deleteUserNotificiationEvent(String notificationEventUuid) throws ChannelException { lock.lock(); try { UserNotificationEventLocalServiceUtil.deleteUserNotificationEvent( notificationEventUuid, getCompanyId()); Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); unconfirmedNotificationEvents.remove(notificationEventUuid); } catch (Exception e) { throw new ChannelException( "Unable to delete event " + notificationEventUuid, e); } finally { lock.unlock(); } } @Override public void deleteUserNotificiationEvents( Collection<String> notificationEventUuids) throws ChannelException { lock.lock(); try { UserNotificationEventLocalServiceUtil.deleteUserNotificationEvents( notificationEventUuids, getCompanyId()); for (String notificationEventUuid : notificationEventUuids) { Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); unconfirmedNotificationEvents.remove(notificationEventUuid); } } catch (Exception e) { throw new ChannelException( "Unable to delete events for user " + getUserId(), e); } finally { lock.unlock(); } } @Override public void flush() { lock.lock(); try { if (_notificationEvents != null) { _notificationEvents.clear(); } } finally { lock.unlock(); } } @Override public void flush(long timestamp) { lock.lock(); try { if (_notificationEvents == null) { return; } Iterator<NotificationEvent> itr = _notificationEvents.iterator(); while (itr.hasNext()) { NotificationEvent notificationEvent = itr.next(); if (notificationEvent.getTimestamp() < timestamp) { itr.remove(); } } } finally { lock.unlock(); } } @Override public List<NotificationEvent> getNotificationEvents(boolean flush) throws ChannelException { lock.lock(); try { return doGetNotificationEvents(flush); } catch (ChannelException ce) { throw ce; } catch (Exception e) { throw new ChannelException(e); } finally { lock.unlock(); } } @Override public void init() throws ChannelException { lock.lock(); try { doInit(); } catch (SystemException se) { throw new ChannelException( "Unable to init channel " + getUserId(), se); } finally { lock.unlock(); } } @Override public void removeTransientNotificationEvents( Collection<NotificationEvent> notificationEvents) { lock.lock(); try { if (_notificationEvents != null) { _notificationEvents.removeAll(notificationEvents); } } finally { lock.unlock(); } } @Override public void removeTransientNotificationEventsByUuid( Collection<String> notificationEventUuids) { Set<String> notificationEventUuidsSet = new HashSet<>( notificationEventUuids); lock.lock(); try { if (_notificationEvents == null) { return; } Iterator<NotificationEvent> itr = _notificationEvents.iterator(); while (itr.hasNext()) { NotificationEvent notificationEvent = itr.next(); if (notificationEventUuidsSet.contains( notificationEvent.getUuid())) { itr.remove(); } } } finally { lock.unlock(); } } @Override public void sendNotificationEvent(NotificationEvent notificationEvent) throws ChannelException { lock.lock(); try { long currentTime = System.currentTimeMillis(); doStoreNotificationEvent(notificationEvent, currentTime); if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED && notificationEvent.isDeliveryRequired()) { UserNotificationEventLocalServiceUtil.addUserNotificationEvent( getUserId(), notificationEvent); } notifyChannelListeners(); } catch (Exception e) { throw new ChannelException("Unable to send event", e); } finally { lock.unlock(); } } @Override public void sendNotificationEvents( Collection<NotificationEvent> notificationEvents) throws ChannelException { lock.lock(); try { long currentTime = System.currentTimeMillis(); List<NotificationEvent> persistedNotificationEvents = new ArrayList<>(notificationEvents.size()); for (NotificationEvent notificationEvent : notificationEvents) { doStoreNotificationEvent(notificationEvent, currentTime); if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED && notificationEvent.isDeliveryRequired()) { persistedNotificationEvents.add(notificationEvent); } } if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED && !persistedNotificationEvents.isEmpty()) { UserNotificationEventLocalServiceUtil.addUserNotificationEvents( getUserId(), persistedNotificationEvents); } notifyChannelListeners(); } catch (Exception e) { throw new ChannelException("Unable to send event", e); } finally { lock.unlock(); } } @Override public void storeNotificationEvent( NotificationEvent notificationEvent, long currentTime) { lock.lock(); try { doStoreNotificationEvent(notificationEvent, currentTime); } finally { lock.unlock(); } } @Override protected void doCleanUp() throws Exception { lock.lock(); try { long currentTime = System.currentTimeMillis(); TreeSet<NotificationEvent> notificationEvents = _getNotificationEvents(); Iterator<NotificationEvent> itr1 = notificationEvents.iterator(); while (itr1.hasNext()) { NotificationEvent notificationEvent = itr1.next(); if (isRemoveNotificationEvent(notificationEvent, currentTime)) { itr1.remove(); } } Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); List<String> invalidNotificationEventUuids = new ArrayList<>( unconfirmedNotificationEvents.size()); Set<Map.Entry<String, NotificationEvent>> unconfirmedNotificationEventsSet = unconfirmedNotificationEvents.entrySet(); Iterator<Map.Entry<String, NotificationEvent>> itr2 = unconfirmedNotificationEventsSet.iterator(); while (itr2.hasNext()) { Map.Entry<String, NotificationEvent> entry = itr2.next(); NotificationEvent notificationEvent = entry.getValue(); if (isRemoveNotificationEvent(notificationEvent, currentTime)) { invalidNotificationEventUuids.add(entry.getKey()); itr2.remove(); } } if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED && !invalidNotificationEventUuids.isEmpty()) { UserNotificationEventLocalServiceUtil. deleteUserNotificationEvents( invalidNotificationEventUuids, getCompanyId()); } } catch (Exception e) { throw new ChannelException( "Unable to clean up channel " + getUserId(), e); } finally { lock.unlock(); } } protected List<NotificationEvent> doGetNotificationEvents(boolean flush) throws Exception { long currentTime = System.currentTimeMillis(); TreeSet<NotificationEvent> notificationEventsSet = _getNotificationEvents(); Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); List<NotificationEvent> notificationEvents = new ArrayList<>( notificationEventsSet.size() + unconfirmedNotificationEvents.size()); for (NotificationEvent notificationEvent : notificationEventsSet) { if (isRemoveNotificationEvent(notificationEvent, currentTime)) { break; } else { notificationEvents.add(notificationEvent); } } if (flush) { notificationEventsSet.clear(); } else if (notificationEventsSet.size() != notificationEvents.size()) { notificationEventsSet.retainAll(notificationEvents); } List<String> invalidNotificationEventUuids = new ArrayList<>( unconfirmedNotificationEvents.size()); Set<Map.Entry<String, NotificationEvent>> unconfirmedNotificationEventsSet = unconfirmedNotificationEvents.entrySet(); Iterator<Map.Entry<String, NotificationEvent>> itr = unconfirmedNotificationEventsSet.iterator(); while (itr.hasNext()) { Map.Entry<String, NotificationEvent> entry = itr.next(); NotificationEvent notificationEvent = entry.getValue(); if (isRemoveNotificationEvent(notificationEvent, currentTime) && !notificationEvent.isArchived()) { invalidNotificationEventUuids.add(notificationEvent.getUuid()); itr.remove(); } else { notificationEvents.add(entry.getValue()); } } if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED && !invalidNotificationEventUuids.isEmpty()) { UserNotificationEventLocalServiceUtil.deleteUserNotificationEvents( invalidNotificationEventUuids, getCompanyId()); } return notificationEvents; } protected void doInit() { if (!PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED) { return; } List<UserNotificationEvent> userNotificationEvents = UserNotificationEventLocalServiceUtil. getDeliveredUserNotificationEvents(getUserId(), false); Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); List<String> invalidNotificationEventUuids = new ArrayList<>( unconfirmedNotificationEvents.size()); long currentTime = System.currentTimeMillis(); for (UserNotificationEvent persistedNotificationEvent : userNotificationEvents) { try { JSONObject payloadJSONObject = JSONFactoryUtil.createJSONObject( persistedNotificationEvent.getPayload()); NotificationEvent notificationEvent = NotificationEventFactoryUtil.createNotificationEvent( persistedNotificationEvent.getTimestamp(), persistedNotificationEvent.getType(), payloadJSONObject); notificationEvent.setDeliveryRequired( persistedNotificationEvent.getDeliverBy()); notificationEvent.setUuid(persistedNotificationEvent.getUuid()); if (isRemoveNotificationEvent(notificationEvent, currentTime)) { invalidNotificationEventUuids.add( notificationEvent.getUuid()); } else { unconfirmedNotificationEvents.put( notificationEvent.getUuid(), notificationEvent); } } catch (JSONException jsone) { _log.error(jsone, jsone); invalidNotificationEventUuids.add( persistedNotificationEvent.getUuid()); } } if (!invalidNotificationEventUuids.isEmpty()) { UserNotificationEventLocalServiceUtil.deleteUserNotificationEvents( invalidNotificationEventUuids, getCompanyId()); } } protected void doStoreNotificationEvent( NotificationEvent notificationEvent, long currentTime) { if (isRemoveNotificationEvent(notificationEvent, currentTime)) { return; } if (PropsValues.USER_NOTIFICATION_EVENT_CONFIRMATION_ENABLED && notificationEvent.isDeliveryRequired()) { Map<String, NotificationEvent> unconfirmedNotificationEvents = _getUnconfirmedNotificationEvents(); unconfirmedNotificationEvents.put( notificationEvent.getUuid(), notificationEvent); } else { TreeSet<NotificationEvent> notificationEvents = _getNotificationEvents(); notificationEvents.add(notificationEvent); if (notificationEvents.size() > PropsValues.NOTIFICATIONS_MAX_EVENTS) { NotificationEvent firstNotificationEvent = notificationEvents.first(); notificationEvents.remove(firstNotificationEvent); } } } protected boolean isRemoveNotificationEvent( NotificationEvent notificationEvent, long currentTime) { if ((notificationEvent.getDeliverBy() != 0) && (notificationEvent.getDeliverBy() <= currentTime)) { return true; } else { return false; } } private TreeSet<NotificationEvent> _getNotificationEvents() { if (_notificationEvents == null) { _notificationEvents = new TreeSet<>(_comparator); } return _notificationEvents; } private Map<String, NotificationEvent> _getUnconfirmedNotificationEvents() { if (_unconfirmedNotificationEvents == null) { _unconfirmedNotificationEvents = new LinkedHashMap<>(); } return _unconfirmedNotificationEvents; } private static final Log _log = LogFactoryUtil.getLog(ChannelImpl.class); private static final Comparator<NotificationEvent> _comparator = new NotificationEventComparator(); private TreeSet<NotificationEvent> _notificationEvents; private Map<String, NotificationEvent> _unconfirmedNotificationEvents; }