All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.net.events.NamedEventInterceptor Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.tangosol.net.events;

import com.tangosol.net.events.annotation.Events;
import com.tangosol.net.events.annotation.Interceptor;
import com.tangosol.net.events.annotation.Interceptor.Order;

import com.tangosol.net.events.partition.PartitionedServiceDispatcher;
import com.tangosol.net.events.partition.TransactionEvent;
import com.tangosol.net.events.partition.TransferEvent;
import com.tangosol.net.events.partition.UnsolicitedCommitEvent;
import com.tangosol.net.events.partition.cache.CacheLifecycleEvent;
import com.tangosol.net.events.partition.cache.CacheLifecycleEventDispatcher;
import com.tangosol.net.events.partition.cache.EntryEvent;
import com.tangosol.net.events.partition.cache.EntryProcessorEvent;

import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.RegistrationBehavior;

import java.lang.annotation.Annotation;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.tangosol.util.Base.azzert;

/**
 * A wrapper for {@link EventInterceptor}s allowing additional metadata to be
 * associated with the interceptor without augmenting the interceptor contract.
 * 

* This implementation will derive defaults for the values it is concerned * with. These defaults are based on the presence of the {@link Interceptor} * annotation, one or more {@link Events} annotations, or a generic type * defined on the EventInterceptor class file. Once this class has been * initialized, via constructors, it is immutable. *

* There may be circumstances when the cost of deriving defaults may not be * required thus a {@link #NamedEventInterceptor(EventInterceptor) constructor} * is provided that avoids this initialization. * * @author hr/nsa/bo 2011.03.29 * @since Coherence 12.1.2 * * @param the type of event */ public class NamedEventInterceptor> implements EventDispatcherAwareInterceptor { // ----- constructors --------------------------------------------------- /** * Construct a NamedEventInterceptor for the specified interceptor. * * @param interceptor the {@link EventInterceptor} */ public NamedEventInterceptor(EventInterceptor interceptor) { this(null, interceptor); } /** * Construct a NamedEventInterceptor for the specified interceptor and * unique identifier. * * @param sName the registered name of the wrapped interceptor * @param interceptor the {@link EventInterceptor} */ public NamedEventInterceptor(String sName, EventInterceptor interceptor) { this(sName, interceptor, null); } /** * Construct a NamedEventInterceptor for the specified interceptor and * unique identifier. * * @param sName the registered name of the wrapped interceptor * @param interceptor the {@link EventInterceptor} * @param behavior the behavior enacted upon discovering duplicate * interceptors */ public NamedEventInterceptor(String sName, EventInterceptor interceptor, RegistrationBehavior behavior) { this(sName, interceptor, null, null, null, behavior); } /** * Construct a NamedEventInterceptor for the specified interceptor. * * @param sName the registered name of the wrapped interceptor * @param interceptor the {@link EventInterceptor} * @param sCacheName the name of the cache this interceptor is * targeted to * @param sServiceName the name of the service this interceptor is * targeted to * @param order whether this interceptor should be first in the * chain of interceptors * @param behavior the behavior enacted upon discovering duplicate * interceptors */ public NamedEventInterceptor(String sName, EventInterceptor interceptor, String sCacheName, String sServiceName, Order order, RegistrationBehavior behavior) { this(sName, interceptor, sCacheName, sServiceName, order, behavior, null); } /** * Construct a NamedEventInterceptor for the specified interceptor using * the provided NamedEventInterceptor as the source to clone from. * * @param interceptor the {@link EventInterceptor} * @param incptrNamed the NamedEventInterceptor to clone from */ public NamedEventInterceptor(EventInterceptor interceptor, NamedEventInterceptor incptrNamed) { this(incptrNamed.getRegisteredName(), interceptor, incptrNamed.getCacheName(), incptrNamed.getServiceName(), incptrNamed.getOrder(), incptrNamed.getBehavior(), incptrNamed.getEventTypes()); } /** * Construct a NamedEventInterceptor for the specified interceptor. * * @param sName the registered name of the wrapped interceptor * @param interceptor the {@link EventInterceptor} * @param sCacheName the name of the cache this interceptor is * targeted to * @param sServiceName the name of the service this interceptor is * targeted to * @param order whether this interceptor should be first in the * chain of interceptors * @param setEventTypes the set of event types the interceptor is * interested in * @param behavior the behavior enacted upon discovering duplicate * interceptors */ public NamedEventInterceptor(String sName, EventInterceptor interceptor, String sCacheName, String sServiceName, Order order, RegistrationBehavior behavior, Set setEventTypes) { azzert(interceptor != null, "interceptor can not be null"); m_sName = sName; m_interceptor = interceptor; m_clzInterceptor = findInterceptorMetadataResolver().getInterceptorClass(interceptor); m_sCacheName = sCacheName != null && sCacheName.isEmpty() ? null : sCacheName; m_sServiceName = sServiceName != null && sServiceName.isEmpty() ? null : sServiceName; m_order = order; m_setEventTypes = setEventTypes == null || setEventTypes.isEmpty() ? null : setEventTypes; m_behavior = behavior; ensureInitialized(); } // ----- accessors ------------------------------------------------------ /** * Return registered name of {@link EventInterceptor}. * * @return registered name of {@link EventInterceptor} */ public String getRegisteredName() { return m_sName; } /** * Return the wrapped interceptor. * * @return the wrapped interceptor */ public EventInterceptor getInterceptor() { return m_interceptor; } /** * Return the set of event types this interceptor is concerned with. * * @return the set of event types this interceptor is concerned with */ public Set getEventTypes() { return m_setEventTypes == null ? null : Collections.unmodifiableSet(m_setEventTypes); } /** * Return the name of the service this interceptor should receive events * from. * * @return the name of the service this interceptor should receive events * from */ public String getServiceName() { return m_sServiceName; } /** * Return the name of the cache this interceptor should receive events * from. * * @return the name of the cache this interceptor should receive events * from */ public String getCacheName() { return m_sCacheName; } /** * Return whether this interceptor should request to be first in the * chain of interceptors. * * @return whether this interceptor should request to be first in the * chain of interceptors */ public boolean isFirst() { return m_order == Interceptor.Order.HIGH; } /** * Return the priority of this interceptor in the chain of interceptors. * * @return the priority of this interceptor in the chain of interceptors */ public Order getOrder() { return m_order; } /** * Return the {@link RegistrationBehavior} associated with this interceptor. * * @return the RegistrationBehavior associated with this interceptor */ public RegistrationBehavior getBehavior() { return m_behavior; } // ----- EventInterceptor interface ------------------------------------- /** * {@inheritDoc} */ @Override public void onEvent(E event) { getInterceptor().onEvent(event); } // ----- EventDispatcherAwareInterceptor interface ---------------------- /** * {@inheritDoc} */ @Override public void introduceEventDispatcher(String sIdentifier, EventDispatcher dispatcher) { EventInterceptor interceptor = getInterceptor(); if (interceptor instanceof EventDispatcherAwareInterceptor && isAcceptable(dispatcher)) { // Note: we restrict EventDispatcherAwareInterceptor implementations // by their scope in the configuration as known by this NamedEventInterceptor ((EventDispatcherAwareInterceptor) interceptor).introduceEventDispatcher(sIdentifier, dispatcher); } else { dispatcher.addEventInterceptor(sIdentifier, this); } } /** * Determine whether the {@link EventDispatcher} should be accepted by * this interceptor. * * @param dispatcher the EventDispatcher being introduced */ public boolean isAcceptable(EventDispatcher dispatcher) { // perform service name and/or cache name filtering String sServiceName = m_sServiceName; String sCacheName = m_sCacheName; String sActualServiceName = null; String sActualCacheName = null; // fast short-circuit if (sServiceName == null && sCacheName == null) { return true; } if (dispatcher instanceof CacheLifecycleEventDispatcher) { CacheLifecycleEventDispatcher bmd = (CacheLifecycleEventDispatcher) dispatcher; sActualServiceName = bmd.getServiceName(); sActualCacheName = bmd.getCacheName(); } else if (dispatcher instanceof PartitionedServiceDispatcher) { sActualServiceName = ((PartitionedServiceDispatcher) dispatcher).getServiceName(); } // predicate boolean fMatch = sServiceName == null || sServiceName.equals(sActualServiceName); if (fMatch && sCacheName != null && sActualCacheName != null) { fMatch = sCacheName.endsWith("*") ? sActualCacheName.startsWith(sCacheName.substring(0, sCacheName.length() - 1)) : sActualCacheName.equals(sCacheName); } return fMatch; } /** * Generates a unique name for the interceptor. The unique name will be * generated by appending a {@code '$'} following by an increasing integer. * * @return a unique name for the interceptor */ public String generateName() { String sName = m_sName; if (sName == null) { sName = (m_sCacheName == null || m_sCacheName.isEmpty() ? "" : m_sCacheName + ":") + (m_sServiceName == null || m_sServiceName.isEmpty() ? "" : m_sServiceName + ":") + m_clzInterceptor.getName(); } else { int iPos = sName.lastIndexOf('$'); if (iPos == -1) { sName += "$1"; } else { int nCurrent = 0; try { nCurrent = Integer.parseInt(sName.substring(iPos + 1)); } catch (NumberFormatException e) { // $ may exist in the name; resort to assuming we have not // generated before sName += "$"; iPos = sName.length() - 1; } sName = sName.substring(0, iPos + 1) + ++nCurrent; } } return m_sName = sName; } // ----- Object methods ------------------------------------------------- /** * {@inheritDoc} */ public String toString() { return "<" + m_sName + ", " + m_clzInterceptor.getName() + ">"; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { return this == o || (o instanceof NamedEventInterceptor ? Base.equals(m_interceptor, ((NamedEventInterceptor) o).m_interceptor) : o instanceof EventInterceptor && Base.equals(m_interceptor, o)); } /** * {@inheritDoc} */ @Override public int hashCode() { return m_interceptor != null ? m_interceptor.hashCode() : 0; } // ----- helpers -------------------------------------------------------- /** * Based on the state of the NamedEventInterceptor populate any * uninitialized values based on either an annotation being present or * the class file's generic types. */ protected final void ensureInitialized() { // process @Interceptor annotation, if present String sName = m_sName; Order order = Order.LOW; Class clzInterceptor = m_clzInterceptor; Interceptor anno = clzInterceptor.getAnnotation(Interceptor.class); Set setEventTypes = new HashSet<>( m_setEventTypes == null ? Collections.emptySet() : m_setEventTypes); if (anno != null) { sName = m_sName = sName == null ? anno.identifier() : sName; order = anno.order(); } // process @Events annotations getAnnotations(clzInterceptor, Events.class) .map(this::getEventTypes) .forEach(setEventTypes::addAll); // process generics iff there are no events specified via the annotations Map mapTypes = setEventTypes.isEmpty() ? ClassHelper.getReifiedTypes(clzInterceptor, EventInterceptor.class) : Collections.emptyMap(); //Note: we could improve the interceptor acceptance by validating the // generic bounds with the annotation event types at the cost of // always calling ClassHelper.getReifiedTypes if (mapTypes.containsKey("E")) { Type[] aTypes = mapTypes.get("E"); if (aTypes[0] != null) { Class clzEvent = ClassHelper.getClass(aTypes[0]); // there are special cases for event grouping interfaces as we // explicitly reference the children and their associated event // types. // Note: we avoid a check for the root interface as null translates // to listening to all event types if (clzEvent == com.tangosol.net.events.partition.Event.class) { setEventTypes.addAll(Arrays.asList(TransactionEvent.Type.values())); setEventTypes.addAll(Arrays.asList(TransferEvent.Type.values())); setEventTypes.addAll(Arrays.asList(UnsolicitedCommitEvent.Type.values())); } else if (clzEvent == com.tangosol.net.events.partition.cache.Event.class) { setEventTypes.addAll(Arrays.asList(EntryEvent.Type.values())); setEventTypes.addAll(Arrays.asList(EntryProcessorEvent.Type.values())); setEventTypes.addAll(Arrays.asList(CacheLifecycleEvent.Type.values())); } else { // we assume events follow the model of having the event type // enum defined as a child of the event class thus can generically // extract the event type enum information. for (Class clzInner : clzEvent.getDeclaredClasses()) { if (clzInner.isEnum()) { setEventTypes.addAll(Arrays.asList((Enum[]) clzInner.getEnumConstants())); break; } } } } } if (!setEventTypes.isEmpty()) { m_setEventTypes = setEventTypes; } // ensure the identifier, order and behavior are populated if (sName == null || sName.isEmpty()) { generateName(); m_behavior = m_behavior == null ? RegistrationBehavior.ALWAYS : m_behavior; } else if (m_behavior == null) { // if a name has been specified by either being provided or via // an annotation we should fail on duplicate registration attempt m_behavior = RegistrationBehavior.FAIL; } m_order = m_order == null ? order : m_order; } /** * Return a stream of annotations on the specified annotated element that * are themselves annotated with the specified meta annotation. * * @param annotatedElement the annotated element to get the annotations from * @param clzMetaAnnotation meta annotation to filter on * * @return a stream of annotations on the specified annotated element that are * annotated with the specified meta annotation */ private Stream getAnnotations(AnnotatedElement annotatedElement, Class clzMetaAnnotation) { return Stream.of(annotatedElement.getAnnotations()) .filter(a -> a.annotationType().isAnnotationPresent(clzMetaAnnotation)); } /** * Return a set of event types the interceptor should handle * based on the specified annotation. * * @param annotation the annotation to get event types from * * @return a set of event types the interceptor should handle */ private Collection getEventTypes(Annotation annotation) { try { // first try to get explicitly specified event types Method methValue = annotation.annotationType().getMethod("value"); Enum[] aTypes = (Enum[]) methValue.invoke(annotation); if (aTypes.length == 0) { // get all event types for a given annotation aTypes = (Enum[]) aTypes.getClass().getComponentType().getEnumConstants(); } return Arrays.asList(aTypes); } catch (Exception e) { throw Base.ensureRuntimeException(e); } } /** * Find the {@link InterceptorMetadataResolver} to use. *

* This method will search for the resolver using {@code ServiceLoader}, and * will either return an instance of a resolver discovered by the loader, or * the default implementation which simply returns the actual class of the * interceptor passed in. *

* If multiple resolvers are discovered by the service loader, this method * will throw an exception. * * @return the {@link InterceptorMetadataResolver} to use * * @throws IllegalStateException if multiple implementations of the * {@link InterceptorMetadataResolver} are discovered */ private static InterceptorMetadataResolver findInterceptorMetadataResolver() { List resolvers = new ArrayList<>(); ServiceLoader.load(InterceptorMetadataResolver.class).forEach(resolvers::add); if (resolvers.isEmpty()) { // no custom resolvers discovered, use the default resolver return interceptor -> interceptor == null ? null : interceptor.getClass(); } else if (resolvers.size() > 1) { // ambiguous, throw an exception String resolverNames = resolvers.stream() .map(resolver -> resolver.getClass().getName()) .collect(Collectors.joining(", ")); throw new IllegalStateException("Found more than one InterceptorMetadataResolver: " + resolverNames); } return resolvers.get(0); } // ----- data members --------------------------------------------------- /** * The event interceptor. */ private final EventInterceptor m_interceptor; /** * The resolved class of the event interceptor. */ private final Class m_clzInterceptor; /** * The cache name this {@link EventInterceptor} is concerned with. */ private final String m_sCacheName; /** * The service name this {@link EventInterceptor} is concerned with. */ private final String m_sServiceName; /** * The registered name associated with the {@link EventInterceptor}. */ private String m_sName; /** * Enum indicating whether this {@link EventInterceptor} should be added * at the head of the {@link EventDispatcher}'s interceptor chain * ({@link Order#HIGH}). */ private Order m_order; /** * The set of event types that this {@link EventInterceptor} subscribes * to. */ private Set m_setEventTypes; /** * Specifies the behavior upon duplicate registration. */ private RegistrationBehavior m_behavior; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy