
com.oracle.coherence.repository.AbstractRepositoryBase Maven / Gradle / Ivy
/*
* Copyright (c) 2021, 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.oracle.coherence.repository;
import com.oracle.coherence.common.base.Exceptions;
import com.oracle.coherence.common.base.Logger;
import com.tangosol.net.NamedMap;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.Filter;
import com.tangosol.util.InvocableMap;
import com.tangosol.util.MapEvent;
import com.tangosol.util.MapListener;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.ValueUpdater;
import com.tangosol.util.extractor.DeserializationAccelerator;
import com.tangosol.util.filter.MapEventFilter;
import com.tangosol.util.function.Remote;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Base functionality for all Coherence
* repository
* implementations.
*
* @param the type of entity's identifier
* @param the type of entity stored in this repository
* @param the underlying map type
*
* @author Ryan Lubke 2021.04.08
* @author Aleks Seovic
* @since 21.06
*/
public abstract class AbstractRepositoryBase
{
// tag::abstract[]
/**
* Return the identifier of the specified entity instance.
*
* @param entity the entity to get the identifier from
*
* @return the identifier of the specified entity instance
*/
protected abstract ID getId(T entity);
/**
* Return the type of entities in this repository.
*
* @return the type of entities in this repository
*/
protected abstract Class extends T> getEntityType();
/**
* Return the map that is used as the underlying entity store.
*
* @return the map that is used as the underlying entity store
*/
protected abstract M getMap();
// end::abstract[]
/**
* Return the underlying {@link NamedMap}. This is used internally
* to support listeners and indices.
*
* @return the underlying {@link NamedMap}
*/
abstract NamedMap getNamedMap();
// ----- helpers --------------------------------------------------------
/**
* Ensures that this repository is initialized by creating necessary indices
* on the backing map.
*
* Base framework classes that extend this class should call this method
* after the backing map has been initialized, but before any other calls
* are made.
*/
protected void ensureInitialized()
{
if (!m_fInitialized)
{
createIndices();
m_fInitialized = true;
}
}
/**
* Creates indices for this repository that are defined via
* {@link Accelerated @Accelerated} and {@link Indexed @Indexed} annotations.
*
* If overriding this method, please call {@code super.createIndices()} or
* the standard behavior will not work.
*/
@SuppressWarnings("unchecked")
protected void createIndices()
{
NamedMap namedMap = getNamedMap();
Class extends T> entityType = getEntityType();
//noinspection CheckStyle
if (getClass().isAnnotationPresent(Accelerated.class) ||
entityType.isAnnotationPresent(Accelerated.class))
{
Logger.info("Configuring deserialization accelerator for " + getClass().getName());
namedMap.addIndex(new DeserializationAccelerator());
}
Stream.of(entityType.getMethods())
.filter(m -> m.isAnnotationPresent(Indexed.class))
.forEach(m ->
{
try
{
Indexed idx = m.getAnnotation(Indexed.class);
boolean fOrdered = idx.ordered();
Comparator> comparator =
Comparator.class.equals(idx.comparator())
? null
: (Comparator>) ClassHelper.newInstance(idx.comparator(), null);
String sIndexMsg = "Creating index %s::%s (ordered=%b, comparator=%s)";
Logger.info(() -> String.format(sIndexMsg, entityType.getSimpleName(),
m.getName(), fOrdered, comparator));
namedMap.addIndex(ValueExtractor.forMethod(m), fOrdered, comparator);
}
catch (Exception e)
{
throw Exceptions.ensureRuntimeException(e);
}
});
}
/**
* An entry processor factory that is used by {@code update} methods that
* accept {@link ValueUpdater} as an argument.
*
* @param updater the updater function to use
* @param value the value to update each entity with, which will be passed
* as an argument to the updater function
* @param factory the entity factory to use to create new entity instance
* @param the type of entity's identifier
* @param the type of entity stored in this repository
* @param the type of value to update
*
* @return an entry processor that should be used to perform the update
*/
static InvocableMap.EntryProcessor updaterProcessor(
ValueUpdater super T, ? super U> updater,
U value,
EntityFactory super ID, ? extends T> factory)
{
return entry ->
{
T entity = entry.getValue();
if (entity == null && factory != null)
{
entity = factory.create(entry.getKey());
}
updater.update(entity, value);
entry.setValue(entity);
return null;
};
}
/**
* An entry processor factory that is used by {@code update} methods that
* accept {@link Remote.Function} as an argument.
*
* @param updater the updater function to use
* @param factory the entity factory to use to create new entity instance
* @param the type of entity's identifier
* @param the type of entity stored in this repository
* @param the type of return value of the updater function
*
* @return an entry processor that should be used to perform the update
*/
static InvocableMap.EntryProcessor updateFunctionProcessor(
Remote.Function super T, ? extends R> updater,
EntityFactory super ID, ? extends T> factory)
{
return entry ->
{
T entity = entry.getValue();
if (entity == null && factory != null)
{
entity = factory.create(entry.getKey());
}
R result = updater.apply(entity);
entry.setValue(entity);
return result;
};
}
/**
* An entry processor factory that is used by {@code update} methods that
* accept {@link Remote.BiFunction} as an argument.
*
* @param updater the updater function to use
* @param value the value to update each entity with, which will be passed
* as an argument to the updater function
* @param factory the entity factory to use to create new entity instance
* @param the type of entity's identifier
* @param the type of entity stored in this repository
* @param the type of value to update
* @param the type of return value of the updater function
*
* @return an entry processor that should be used to perform the update
*/
static InvocableMap.EntryProcessor updateBiFunctionProcessor(
Remote.BiFunction super T, ? super U, ? extends R> updater,
U value,
EntityFactory super ID, ? extends T> factory)
{
return entry ->
{
T entity = entry.getValue();
if (entity == null && factory != null)
{
entity = factory.create(entry.getKey());
}
R result = updater.apply(entity, value);
entry.setValue(entity);
return result;
};
}
// ----- listener support -----------------------------------------------
/**
* Factory method for {@link Listener} adapter to {@link MapListener}.
*
* @param delegate the {@link Listener} to delegate events to
*
* @return a {@link MapListener} that can be registered with a {@link NamedMap}
* and will delegate events to the {@link Listener} it wraps
*/
protected MapListener super ID, ? super T> instantiateMapListener(Listener super T> delegate)
{
return new MapListenerAdapter<>(delegate);
}
/**
* Register a listener that will observe all repository events.
*
* @param listener the event listener to register
*/
public void addListener(Listener super T> listener)
{
getNamedMap().addMapListener(instantiateMapListener(listener));
}
/**
* Unregister a listener that observes all repository events.
*
* @param listener the event listener to unregister
*/
public void removeListener(Listener super T> listener)
{
getNamedMap().removeMapListener(instantiateMapListener(listener));
}
/**
* Register a listener that will observe all events for a specific entity.
*
* @param id the identifier of the entity to observe
* @param listener the event listener to register
*/
public void addListener(ID id, Listener super T> listener)
{
getNamedMap().addMapListener(instantiateMapListener(listener), id, false);
}
/**
* Unregister a listener that observes all events for a specific entity.
*
* @param id the identifier of the entity to observe
* @param listener the event listener to unregister
*/
public void removeListener(ID id, Listener super T> listener)
{
getNamedMap().removeMapListener(instantiateMapListener(listener), id);
}
/**
* Register a listener that will observe all events for entities that
* satisfy the specified criteria.
*
* @param filter the criteria to use to select entities to observe
* @param listener the event listener to register
*/
public void addListener(Filter> filter, Listener super T> listener)
{
if (!(filter instanceof MapEventFilter))
{
filter = new MapEventFilter<>(MapEventFilter.E_ALL, filter);
}
getNamedMap().addMapListener(instantiateMapListener(listener), filter, false);
}
/**
* Unregister a listener that observes all events for entities that satisfy
* the specified criteria.
*
* @param filter the criteria to use to select entities to observe
* @param listener the event listener to unregister
*/
public void removeListener(Filter> filter, Listener super T> listener)
{
if (!(filter instanceof MapEventFilter))
{
filter = new MapEventFilter<>(MapEventFilter.E_ALL, filter);
}
getNamedMap().removeMapListener(instantiateMapListener(listener), filter);
}
/**
* Create new {@link Listener.Builder} instance.
*
* @return a new {@link Listener.Builder} instance
*/
public Listener.Builder listener()
{
return Listener.builder();
}
// ---- inner interface: Listener ---------------------------------------
/**
* An interface that should be implemented by the clients interested in
* repository events.
*
* @param the entity type
*/
public interface Listener
{
/**
* An event callback that will be called when a new entity is inserted
* into the repository.
*
* @param entity inserted entity
*/
default void onInserted(T entity)
{
}
/**
* An event callback that will be called when an entity is updated.
*
* @param oldEntity previous entity
* @param newEntity new entity
*/
default void onUpdated(T oldEntity, T newEntity)
{
}
/**
* An event callback that will be called when an entity is removed from
* the repository.
*
* @param entity removed entity
*/
default void onRemoved(T entity)
{
}
/**
* Create new {@link Listener.Builder} instance.
*
* @param the entity type
*
* @return a new {@link Listener.Builder} instance
*/
static Builder builder()
{
return new Builder<>();
}
/**
* A builder for a simple, lambda-based {@link Listener}.
*
* @param the entity type
*/
class Builder
{
/**
* Build {@link Listener} instance.
*
* @return the {@link Listener} instance
*/
public Listener build()
{
return new DefaultListener<>(m_onInsert, m_onUpdate, m_onRemove);
}
// ---- event handler registration methods ----------------------
/**
* Add the event handler for INSERT events.
*
* The specified {@code eventHandler} will receive the inserted
* entity as an argument when fired.
*
* @param eventHandler the event handler to add
*
* @return this {@link Builder}
*/
public Builder onInsert(Consumer eventHandler)
{
m_onInsert = addHandler(m_onInsert, eventHandler);
return this;
}
/**
* Add the event handler for UPDATE events.
*
* The specified {@code eventHandler} will receive the new value
* of the updated entity as an argument when fired.
*
* @param eventHandler the event handler to execute
*
* @return this Listener
*/
public Builder onUpdate(Consumer eventHandler)
{
m_onUpdate = addHandler(m_onUpdate, (tOld, tNew) -> eventHandler.accept(tNew));
return this;
}
/**
* Add the event handler for UPDATE events.
*
* The specified {@code eventHandler} will receive both the old and
* the new value of the updated entity as arguments when fired.
*
* @param eventHandler the event handler to execute
*
* @return this Listener
*/
public Builder onUpdate(BiConsumer eventHandler)
{
m_onUpdate = addHandler(m_onUpdate, eventHandler);
return this;
}
/**
* Add the event handler for REMOVE events.
*
* The specified {@code eventHandler} will receive the removed
* entity as an argument when fired.
*
* @param eventHandler the event handler to execute
*
* @return this Listener
*/
public Builder onRemove(Consumer eventHandler)
{
m_onRemove = addHandler(m_onRemove, eventHandler);
return this;
}
/**
* Add the event handler for all events.
*
* The specified {@code eventHandler} will receive the new value of
* the inserted or updated entity, and the old value of the removed
* entity as an argument when fired.
*
* @param eventHandler the event handler to execute
*
* @return this MapListener
*/
public Builder onEvent(Consumer eventHandler)
{
onInsert(eventHandler);
onUpdate(eventHandler);
onRemove(eventHandler);
return this;
}
// ---- helper methods ------------------------------------------
/**
* Add a handler to a handler chain.
*
* @param handlerChain the existing handler chain (could be null)
* @param handler the handler to add
*
* @return new handler chain
*/
private Consumer addHandler(Consumer handlerChain, Consumer handler)
{
return handlerChain == null
? handler
: handlerChain.andThen(handler);
}
/**
* Add a handler to a handler chain.
*
* @param handlerChain the existing handler chain (could be null)
* @param handler the handler to add
*
* @return new handler chain
*/
private BiConsumer addHandler(BiConsumer handlerChain, BiConsumer handler)
{
return handlerChain == null
? handler
: handlerChain.andThen(handler);
}
// ---- data members ------------------------------------------------
/**
* The event handler to execute on INSERT event.
*/
private Consumer m_onInsert;
/**
* The event handler to execute on UPDATE event.
*/
private BiConsumer m_onUpdate;
/**
* The event handler to execute on REMOVE event.
*/
private Consumer m_onRemove;
}
}
// ---- inner class: DefaultListener ------------------------------------
/**
* Simple {@link Listener} implementation that delegates each event to
* the consumer functions it was constructed with.
*
* @param the entity type
*/
static class DefaultListener
implements Listener
{
// ---- constructor -------------------------------------------------
/**
* Construct {@link DefaultListener} instance.
*
* @param onInsert the consumer function to delegate INSERT events to
* @param onUpdate the consumer function to delegate UPDATE events to
* @param onRemove the consumer function to delegate REMOVE events to
*/
DefaultListener(Consumer onInsert, BiConsumer onUpdate, Consumer onRemove)
{
m_onInsert = onInsert;
m_onUpdate = onUpdate;
m_onRemove = onRemove;
}
// ---- Listener interface ------------------------------------------
@Override
public void onInserted(T entity)
{
if (m_onInsert != null)
{
m_onInsert.accept(entity);
}
}
@Override
public void onUpdated(T entityOld, T entityNew)
{
if (m_onUpdate != null)
{
m_onUpdate.accept(entityOld, entityNew);
}
}
@Override
public void onRemoved(T entity)
{
if (m_onRemove != null)
{
m_onRemove.accept(entity);
}
}
// ---- data members ------------------------------------------------
/**
* The event handler to execute on INSERT event.
*/
private final Consumer m_onInsert;
/**
* The event handler to execute on UPDATE event.
*/
private final BiConsumer m_onUpdate;
/**
* The event handler to execute on REMOVE event.
*/
private final Consumer m_onRemove;
}
// ---- inner class: MapListenerAdapter ---------------------------------
/**
* Adapter from {@link Listener} to {@link MapListener} that can be
* registered with the backing {@link NamedMap}.
*
* @param the type of entity's identifier
* @param the type of entity
*/
protected static class MapListenerAdapter
implements MapListener
{
/**
* Construct MapListenerAdapter instance.
*
* @param listener the listener to adapt
*/
MapListenerAdapter(Listener super T> listener)
{
this.m_listener = listener;
}
// ---- MapListener interface ---------------------------------------
@Override
public void entryInserted(MapEvent mapEvent)
{
m_listener.onInserted(mapEvent.getNewValue());
}
@Override
public void entryUpdated(MapEvent mapEvent)
{
m_listener.onUpdated(mapEvent.getOldValue(), mapEvent.getNewValue());
}
@Override
public void entryDeleted(MapEvent mapEvent)
{
m_listener.onRemoved(mapEvent.getOldValue());
}
// ---- Object methods ----------------------------------------------
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
MapListenerAdapter, ?> that = (MapListenerAdapter, ?>) o;
return m_listener.equals(that.m_listener);
}
@Override
public int hashCode()
{
return Objects.hash(m_listener);
}
// ---- data members ------------------------------------------------
/**
* The listener to adapt.
*/
private final Listener super T> m_listener;
}
// ---- data members ----------------------------------------------------
/**
* Flag indicating initialization status.
*/
private volatile boolean m_fInitialized;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy