AlbumMediaNotificationManager.java
/*
* Copyright (C) 2000 - 2024 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "https://www.silverpeas.org/legal/floss_exception.html"
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.silverpeas.components.gallery.notification.user;
import org.silverpeas.components.gallery.model.AlbumDetail;
import org.silverpeas.components.gallery.model.AlbumMedia;
import org.silverpeas.components.gallery.model.Media;
import org.silverpeas.core.admin.user.model.User;
import org.silverpeas.core.annotation.Service;
import org.silverpeas.core.notification.user.builder.helper.UserNotificationHelper;
import org.silverpeas.core.scheduler.Job;
import org.silverpeas.core.scheduler.JobExecutionContext;
import org.silverpeas.core.scheduler.Scheduler;
import org.silverpeas.core.scheduler.SchedulerException;
import org.silverpeas.core.scheduler.SchedulerProvider;
import org.silverpeas.core.scheduler.trigger.JobTrigger;
import org.silverpeas.kernel.bundle.ResourceLocator;
import org.silverpeas.core.util.ServiceProvider;
import org.silverpeas.kernel.bundle.SettingBundle;
import org.silverpeas.kernel.logging.SilverLogger;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author silveryocha
*/
@Service
public class AlbumMediaNotificationManager {
private static final ConcurrentMap<String, NotificationAlbumJob> contextCache =
new ConcurrentHashMap<>();
AlbumMediaNotificationManager() {
}
private static String buildKey(final User user, final AlbumMedia albumMedia) {
return buildKey(user, albumMedia.getMedia().getInstanceId(), albumMedia.getAlbumId());
}
private static String buildKey(final User user, final String instanceId, String albumId) {
return user.getId() + "@" + instanceId + "@" + albumId;
}
public static AlbumMediaNotificationManager get() {
return ServiceProvider.getService(AlbumMediaNotificationManager.class);
}
/**
* Puts a creation of an {@link AlbumMedia} into the context.
* @param albumMedia the couple of an album and a media.
* @param by the user associated to {@link AlbumMedia}.
*/
public void putCreationOf(final AlbumMedia albumMedia, final User by) {
final NotificationAlbumJob job = getNotificationAlbumJob(by, albumMedia);
job.registerCreation(albumMedia.getMedia());
}
/**
* Puts a deletion of an {@link AlbumMedia} into the context.
* @param albumMedia the couple of an album and a media.
* @param by the user associated to {@link AlbumMedia}.
*/
public void putDeletionOf(final AlbumMedia albumMedia, final User by) {
final NotificationAlbumJob job = getNotificationAlbumJob(by, albumMedia);
job.registerDeletion(albumMedia.getMedia());
}
private NotificationAlbumJob getNotificationAlbumJob(final User user,
final AlbumMedia albumMedia) {
final String key = buildKey(user, albumMedia);
return contextCache
.computeIfAbsent(key, k -> new NotificationAlbumJob(user, albumMedia.getAlbum()));
}
/**
* Context of {@link Media} movements according to an album.
*/
static class NotificationAlbumJob extends Job {
final Set<Media> creations = Collections.synchronizedSet(new HashSet<>());
final User sender;
final String instanceId;
final AlbumDetail album;
private final Object mutex;
private NotificationAlbumJob(final User sender, final AlbumDetail album) {
super("JOB_NAME_" + UUID.randomUUID().toString());
this.mutex = creations;
this.sender = sender;
this.instanceId = album.getNodePK().getInstanceId();
this.album = album;
}
private void setJobDelay() {
final SettingBundle settings = ResourceLocator
.getSettingBundle("org.silverpeas.gallery.settings.gallerySettings");
final int delayInSeconds = settings.getInteger("subscription.notification.delay", 180);
boolean sendImmediately = delayInSeconds <= 0;
if (!sendImmediately) {
final Scheduler scheduler = SchedulerProvider.getVolatileScheduler();
final OffsetDateTime executionTime = OffsetDateTime.now().plusSeconds(delayInSeconds);
try {
scheduler.unscheduleJob(getName());
if (isMediaToProcess()) {
scheduler.scheduleJob(this, JobTrigger.triggerAt(executionTime));
} else {
clearJobCache();
}
} catch (SchedulerException e) {
SilverLogger.getLogger(this).error(e);
sendImmediately = true;
}
}
if (sendImmediately) {
execute(null);
}
}
void registerCreation(final Media media) {
synchronized (mutex) {
creations.add(media);
setJobDelay();
}
putIntoCache();
}
void registerDeletion(final Media media) {
synchronized (mutex) {
creations.remove(media);
setJobDelay();
}
putIntoCache();
}
@Override
public void execute(final JobExecutionContext context) {
clearJobCache();
final Media[] creationsToProcess;
synchronized (mutex) {
creationsToProcess = creations.toArray(new Media[0]);
creations.clear();
}
if (creationsToProcess.length > 0) {
final GalleryAlbumMediaSubscriptionNotificationBuilder builder =
new GalleryAlbumMediaSubscriptionNotificationBuilder(album, sender)
.aboutMedia(creationsToProcess);
UserNotificationHelper.buildAndSend(builder);
}
}
private void clearJobCache() {
final String cacheKey = buildKey(sender, instanceId, album.getId());
contextCache.remove(cacheKey);
}
private boolean isMediaToProcess() {
return !creations.isEmpty();
}
private void putIntoCache() {
if (isMediaToProcess()) {
contextCache.put(buildKey(sender, instanceId, album.getId()),
NotificationAlbumJob.this);
}
}
}
}