/* * * * Copyright (c) 2016. David Sowerby * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * * specific language governing permissions and limitations under the License. * */ package uk.q3c.krail.core.push; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Singleton; import com.vaadin.ui.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.q3c.krail.core.config.ApplicationConfiguration; import uk.q3c.krail.core.config.ConfigKeys; import uk.q3c.krail.core.guice.uiscope.UIKey; import uk.q3c.krail.core.ui.ScopedUI; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Singleton @ThreadSafe public class DefaultBroadcaster implements Broadcaster { private static Logger log = LoggerFactory.getLogger(DefaultBroadcaster.class); private final Map<String, List<BroadcastListener>> groups = new HashMap<>(); private final List<BroadcastListener> allGroup = new ArrayList<>(); private final ApplicationConfiguration applicationConfiguration; private final AtomicInteger messageCount; @Inject protected DefaultBroadcaster(ApplicationConfiguration applicationConfiguration) { this.applicationConfiguration = applicationConfiguration; messageCount = new AtomicInteger(0); } @Override public synchronized Broadcaster register(@Nonnull String group, @Nonnull BroadcastListener listener) { checkNotNull(group); checkNotNull(listener); checkArgument(!group.isEmpty(), "Group should not be an empty String"); log.debug("adding listener: {}", listener.getClass() .getName()); if (ALL_MESSAGES.equals(group)) { allGroup.add(listener); } else { List<BroadcastListener> listenerGroup = groups.get(group); if (listenerGroup == null) { listenerGroup = new ArrayList<>(); groups.put(group, listenerGroup); } listenerGroup.add(listener); } return this; } @Override public synchronized Broadcaster unregister(@Nonnull String group, @Nonnull BroadcastListener listener) { checkNotNull(group); checkNotNull(listener); if (ALL_MESSAGES.equals(group)) { allGroup.remove(listener); } else { List<BroadcastListener> listenerGroup = groups.get(group); if (listenerGroup != null) { listenerGroup.remove(listener); } } return this; } @Override public synchronized Broadcaster broadcast(@Nonnull final String group, @Nonnull final String message, @Nonnull Component sender) { checkNotNull(sender); ScopedUI scopedUI = (ScopedUI) sender.getUI(); return broadcast(group, message, scopedUI.getInstanceKey()); } @Override public synchronized Broadcaster broadcast(@Nonnull final String group, @Nonnull final String message, @Nonnull UIKey sender) { checkNotNull(group); checkNotNull(message); checkArgument(!group.isEmpty(), "Group should not be an empty String"); int messageId = messageCount.incrementAndGet(); ExecutorService executorService = Executors.newSingleThreadExecutor(); if (applicationConfiguration.getBoolean(ConfigKeys.SERVER_PUSH_ENABLED, true)) { log.debug("broadcasting message: {} from: {}", messageId, sender); List<BroadcastListener> listenerGroup = groups.get(group); if (listenerGroup != null) { for (final BroadcastListener listener : listenerGroup) executorService.execute(() -> listener.receiveBroadcast(group, message, sender, messageId)); } for (final BroadcastListener listener : allGroup) executorService.execute(() -> listener.receiveBroadcast(group, message, sender, messageId)); } else { log.debug("server push is disabled, message not broadcast"); } closeExecutor(executorService); return this; } @Override @Nonnull public ImmutableList<BroadcastListener> getListenerGroup(@Nonnull String group) { checkNotNull(group); checkArgument(!group.isEmpty(), "Group should not be an empty String"); if (Broadcaster.ALL_MESSAGES.equals(group)) { return ImmutableList.copyOf(allGroup); } List<BroadcastListener> listenerGroup = groups.get(group); if (listenerGroup == null) { return ImmutableList.of(); } return ImmutableList.copyOf(listenerGroup); } /** * Stops the @{code executor} with appropriate timeouts and logging * * @param executor the Executor to be shut down. */ protected void closeExecutor(ExecutorService executor) { try { log.debug("Closing Executor, attempt to shutdown executor"); executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { log.error("Thread interrupted while shutting down Executor"); } finally { if (!executor.isTerminated()) { log.error("forcing shutdown"); } executor.shutdownNow(); log.info("Services Executor shutdown finished"); } } }