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

com.oracle.coherence.repository.AbstractRepositoryBase Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * 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 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 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 updater, U value, EntityFactory 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 updater, EntityFactory 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 updater, U value, EntityFactory 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 instantiateMapListener(Listener 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 listener) { getNamedMap().addMapListener(instantiateMapListener(listener)); } /** * Unregister a listener that observes all repository events. * * @param listener the event listener to unregister */ public void removeListener(Listener 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 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 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 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 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 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 m_listener; } // ---- data members ---------------------------------------------------- /** * Flag indicating initialization status. */ private volatile boolean m_fInitialized; }