/* * (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others. * * 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. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.pubsub; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.event.EventServiceComponent; import org.nuxeo.runtime.model.ComponentContext; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.DefaultComponent; /** * Implementation for the Publish/Subscribe Service. * * @since 9.1 */ public class PubSubServiceImpl extends DefaultComponent implements PubSubService { public static final String CONFIG_XP = "configuration"; /** All the registered descriptors. */ protected List<PubSubProviderDescriptor> providerDescriptors = new CopyOnWriteArrayList<>(); /** The currently-configured provider. */ protected PubSubProvider provider; /** The descriptor for the currently-configured provider, or {@code null} if it's the default. */ protected PubSubProviderDescriptor providerDescriptor; /** List of subscribers for each topic. */ protected Map<String, List<BiConsumer<String, byte[]>>> subscribers = new ConcurrentHashMap<>(); @Override public void activate(ComponentContext context) { providerDescriptorChanged(); } @Override public void deactivate(ComponentContext context) { subscribers.clear(); provider.close(); provider = null; } @Override public void applicationStarted(ComponentContext context) { if (provider == null) { return; } provider.initialize(subscribers); } @Override public void applicationStopped(ComponentContext context, Instant deadline) { if (provider == null) { return; } provider.close(); } @Override public int getApplicationStartedOrder() { // let RedisComponent start before us (Redis starts before WorkManager that starts before events) return EventServiceComponent.APPLICATION_STARTED_ORDER + 10; } @Override public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (CONFIG_XP.equals(extensionPoint)) { registerProvider((PubSubProviderDescriptor) contribution); } else { throw new NuxeoException("Unknown extension point: " + extensionPoint); } } @Override public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (CONFIG_XP.equals(extensionPoint)) { unregisterProvider((PubSubProviderDescriptor) contribution); } } protected void registerProvider(PubSubProviderDescriptor descriptor) { providerDescriptors.add(descriptor); providerDescriptor = descriptor; providerDescriptorChanged(); } protected void unregisterProvider(PubSubProviderDescriptor descriptor) { providerDescriptors.remove(descriptor); if (descriptor == providerDescriptor) { // we removed the current provider, find a new one int size = providerDescriptors.size(); providerDescriptor = size == 0 ? null : providerDescriptors.get(size - 1); providerDescriptorChanged(); } } protected void providerDescriptorChanged() { if (provider != null) { provider.close(); } if (providerDescriptor == null) { provider = new MemPubSubProvider(); // default implementation } else { provider = providerDescriptor.getInstance(); } // initialize later, in applicationStarted // provider.initialize(subscribers); } // ===== delegation to actual implementation ===== @Override public void publish(String topic, byte[] message) { provider.publish(topic, message); } @Override public void registerSubscriber(String topic, BiConsumer<String, byte[]> subscriber) { subscribers.computeIfAbsent(topic, k -> new CopyOnWriteArrayList<>()).add(subscriber); } @Override public void unregisterSubscriber(String topic, BiConsumer<String, byte[]> subscriber) { // use computeIfAbsent also for removal to avoid thread-safety issues subscribers.computeIfAbsent(topic, k -> new CopyOnWriteArrayList<>()).remove(subscriber); } }