/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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 com.hazelcast.spi.impl.eventservice.impl; import com.hazelcast.internal.metrics.Probe; import com.hazelcast.nio.Address; import com.hazelcast.spi.EventFilter; import com.hazelcast.spi.ListenerWrapperEventFilter; import com.hazelcast.spi.NotifiableEventListener; import com.hazelcast.util.ConcurrencyUtil; import com.hazelcast.util.ConstructorFunction; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; /** * Segment of the event service. Each segment is responsible for a single service and * holds {@link Registration}s for that service. The segment is responsible for keeping the * number of publications and keeping the registrations. * The listener registration defines a specific topic that allows for further granulation of * the published events. This allows events to be published for the same service but for * a different topic (e.g. for a map service the topics are specific map instance names). * * @param <S> the service type for which this segment is responsible */ public class EventServiceSegment<S> { /** The name of the service for which this segment is responsible */ private final String serviceName; /** The service for which this segment is responsible */ private final S service; /** Map of {@link Registration}s grouped by event topic */ private final ConcurrentMap<String, Collection<Registration>> registrations = new ConcurrentHashMap<String, Collection<Registration>>(); /** Registration ID to registration map */ @Probe(name = "listenerCount") private final ConcurrentMap<String, Registration> registrationIdMap = new ConcurrentHashMap<String, Registration>(); @Probe(name = "publicationCount") private final AtomicLong totalPublishes = new AtomicLong(); public EventServiceSegment(String serviceName, S service) { this.serviceName = serviceName; this.service = service; } /** * Returns the number of registrations for this segment. */ private int listenerCount() { return registrationIdMap.size(); } /** * Notifies the registration of a event in the listener lifecycle. * The registration is checked for a {@link NotifiableEventListener} in this order : * <ul> * <li>first check if {@link Registration#getListener()} returns a {@link NotifiableEventListener}</li> * <li>otherwise check if the event filter wraps a listener and use that one</li> * </ul> * * @param topic the event topic * @param registration the listener registration * @param register if the listener was registered or not */ private void pingNotifiableEventListener(String topic, Registration registration, boolean register) { Object listener = registration.getListener(); if (!(listener instanceof NotifiableEventListener)) { EventFilter filter = registration.getFilter(); if (filter instanceof ListenerWrapperEventFilter) { listener = ((ListenerWrapperEventFilter) filter).getListener(); } } pingNotifiableEventListenerInternal(listener, topic, registration, register); pingNotifiableEventListenerInternal(service, topic, registration, register); } /** * Notifies the object of an event in the lifecycle of the listener. The listener may have * been registered or deregistered. The object must implement {@link NotifiableEventListener} if * it wants to be notified. * * @param object the object to notified. It must implement {@link NotifiableEventListener} to be notified * @param topic the event topic name * @param registration the listener registration * @param register whether the listener was registered or not */ private void pingNotifiableEventListenerInternal(Object object, String topic, Registration registration, boolean register) { if (!(object instanceof NotifiableEventListener)) { return; } NotifiableEventListener notifiableEventListener = ((NotifiableEventListener) object); if (register) { notifiableEventListener.onRegister(service, serviceName, topic, registration); } else { notifiableEventListener.onDeregister(service, serviceName, topic, registration); } } /** * Returns the {@link Registration}s for the event {@code topic}. If there are no * registrations and {@code forceCreate}, it will create a concurrent set and put it in the registration map. * * @param topic the event topic * @param forceCreate whether to create the registration set if none exists or to return null * @return the collection of registrations for the topic or null if none exists and {@code forceCreate} is {@code false} */ public Collection<Registration> getRegistrations(String topic, boolean forceCreate) { Collection<Registration> listenerList = registrations.get(topic); if (listenerList == null && forceCreate) { ConstructorFunction<String, Collection<Registration>> func = new ConstructorFunction<String, Collection<Registration>>() { public Collection<Registration> createNew(String key) { return Collections.newSetFromMap(new ConcurrentHashMap<Registration, Boolean>()); } }; return ConcurrencyUtil.getOrPutIfAbsent(registrations, topic, func); } return listenerList; } /** * Returns the map from registration ID to the listener registration. */ public ConcurrentMap<String, Registration> getRegistrationIdMap() { return registrationIdMap; } /** * Adds a registration for the {@code topic} and notifies the listener and service of the listener * registration. Returns if the registration was added. The registration might not be added * if an equal instance is already registered. * * @param topic the event topic * @param registration the listener registration * @return if the registration is added */ public boolean addRegistration(String topic, Registration registration) { final Collection<Registration> registrations = getRegistrations(topic, true); if (registrations.add(registration)) { registrationIdMap.put(registration.getId(), registration); pingNotifiableEventListener(topic, registration, true); return true; } return false; } /** * Removes the registration matching the {@code topic} and {@code id}. * Returns the removed registration or {@code null} if none matched. * * @param topic the registration topic name * @param id the registration ID * @return the registration which was removed or {@code null} if none matchec */ public Registration removeRegistration(String topic, String id) { final Registration registration = registrationIdMap.remove(id); if (registration != null) { final Collection<Registration> all = registrations.get(topic); if (all != null) { all.remove(registration); } pingNotifiableEventListener(topic, registration, false); } return registration; } /** * Removes all registrations for the specified topic and notifies the listeners and * service of the listener deregistrations. * * @param topic the topic for which registrations are removed */ void removeRegistrations(String topic) { final Collection<Registration> all = registrations.remove(topic); if (all != null) { for (Registration reg : all) { registrationIdMap.remove(reg.getId()); pingNotifiableEventListener(topic, reg, false); } } } void clear() { for (Collection<Registration> all : registrations.values()) { Iterator<Registration> iter = all.iterator(); while (iter.hasNext()) { Registration reg = iter.next(); iter.remove(); registrationIdMap.remove(reg.getId()); pingNotifiableEventListener(reg.getTopic(), reg, false); } } } void onMemberLeft(Address address) { for (Collection<Registration> all : registrations.values()) { Iterator<Registration> iter = all.iterator(); while (iter.hasNext()) { Registration reg = iter.next(); if (address.equals(reg.getSubscriber())) { iter.remove(); registrationIdMap.remove(reg.getId()); pingNotifiableEventListener(reg.getTopic(), reg, false); } } } } long incrementPublish() { return totalPublishes.incrementAndGet(); } boolean hasRegistration(String topic) { Collection<Registration> topicRegistrations = registrations.get(topic); return !(topicRegistrations == null || topicRegistrations.isEmpty()); } }