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

com.oracle.coherence.patterns.messaging.AbstractSubscriber Maven / Gradle / Ivy

There is a newer version: 13.0.1
Show newest version
/*
 * File: AbstractSubscriber.java
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * The contents of this file are subject to the terms and conditions of 
 * the Common Development and Distribution License 1.0 (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License by consulting the LICENSE.txt file
 * distributed with this file, or by consulting
 * or https://oss.oracle.com/licenses/CDDL
 *
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file LICENSE.txt.
 *
 * MODIFICATIONS:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 */

package com.oracle.coherence.patterns.messaging;

import com.oracle.coherence.common.identifiers.Identifier;

import com.oracle.coherence.common.leasing.Lease;

import com.oracle.coherence.patterns.messaging.entryprocessors.UnsubscribeProcessor;
import com.oracle.coherence.patterns.messaging.exceptions.SubscriberInterruptedException;
import com.oracle.coherence.patterns.messaging.exceptions.SubscriptionLostException;

import com.tangosol.net.CacheFactory;

import com.tangosol.util.MapEvent;
import com.tangosol.util.MapListener;

import com.tangosol.util.processor.UpdaterProcessor;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The base implementation of a {@link Subscriber} for
 * {@link LeasedSubscription}s.
 * 

* Copyright (c) 2008. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates. * * @param subscription * * @author Brian Oliver */ abstract class AbstractSubscriber implements Subscriber { /** * The internal {@link State} of the {@link Subscriber}. */ protected enum State { /** * Starting indicates that the {@link Subscriber} has started * but is yet to receive it's {@link Subscription} state from the cluster. */ Starting, /** * Active indicates that the {@link Subscriber} is ready for use. */ Active, /** * Interrupted indicates that the {@link Subscriber} was interrupted while * attempting to perform some action. This is a terminating state. */ Interrupted, /** * Removed indicates that the {@link Subscription} for the {@link Subscriber} * has been removed. This is a terminating state. */ Removed, /** * Shutdown indicates that the {@link Subscriber} has been shutdown. * This is a terminating state. */ Shutdown } /** * Logger */ private static Logger logger = Logger.getLogger(AbstractSubscriber.class.getName()); /** * The {@link MessagingSession} that owns this {@link Subscriber}. */ private MessagingSession messagingSession; /** * The {@link Identifier} allocated to the {@link Subscriber}. */ private SubscriptionIdentifier subscriptionIdentifier; /** * This {@link Queue} is used by the {@link Subscriber} to wait for updates * on it's {@link Subscription} to arrive. This is especially especially useful * to we known when {@link Message}s have arrived. */ private LinkedBlockingQueue subscriptionUpdates; /** * Should messages received during a getMessage be automatically * acknowledged (and thus committed as read). */ private boolean autocommit; /** * True if a subscription was offered at least once. */ private boolean subscriptionOffered; /** * The {@link MapListener} that will be used to receive call back events * (and new {@link Subscription} state) from the cluster. */ private MapListener mapListener; /** * A {@link Thread} that is responsible for maintaining the * {@link Lease} associated with this {@link Subscription}. */ private LeaseMaintainerThread leaseMaintainerThread; /** * A map that keeps track of the previous sequence number received by this subscriber by a given publisher. * This is used to verify that sequence numbers are in order for a given publisher. */ private ConcurrentHashMap prevMessageIdentifierMap = new ConcurrentHashMap(); /** * State of the subscriber. */ protected State state; /** * Protected Constructor. * * @param messagingSession The {@link MessagingSession} that owns this {@link Subscriber}. * @param subscriptionIdentifier subscription identifier */ protected AbstractSubscriber(MessagingSession messagingSession, SubscriptionIdentifier subscriptionIdentifier) { this.messagingSession = messagingSession; this.subscriptionIdentifier = subscriptionIdentifier; this.subscriptionUpdates = new LinkedBlockingQueue(); this.autocommit = true; this.subscriptionOffered = false; this.state = State.Starting; // register a MapListener that keeps our local copy of the subscription // up-to-date this.mapListener = new MapListener() { @SuppressWarnings("unchecked") public void entryInserted(MapEvent mapEvent) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "{0} received insert event {1}", new Object[] {this, mapEvent}); } // added the newly arrived subscription state to the queue for // internal processing subscriptionOffered = true; subscriptionUpdates.offer((S) mapEvent.getNewValue()); } @SuppressWarnings("unchecked") public void entryUpdated(MapEvent mapEvent) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "{0} received update event {1}", new Object[] {this, mapEvent}); } S oldSubscription = (S) mapEvent.getOldValue(); S newSubscription = (S) mapEvent.getNewValue(); // only queue the updated subscription if the event was a result // of; // 1. the visible range has increased in size (ie: a message was // delivered) // 2. a lease was unsuspended if (oldSubscription.getNumMessages() < newSubscription.getNumMessages() || (((LeasedSubscription) oldSubscription).getLease().isSuspended() &&!((LeasedSubscription) newSubscription).getLease().isSuspended())) { // added the updated arrived subscription state to the queue // for internal processing subscriptionOffered = true; subscriptionUpdates.offer((S) mapEvent.getNewValue()); } else if ((getState() == State.Starting) && (!subscriptionOffered)) { // The application just subscribed using an existing subscription (e.g. durable subscription). // Add the subscription to the queue so the subscriber can move to the Active state. subscriptionOffered = true; subscriptionUpdates.offer((S) mapEvent.getNewValue()); } } @SuppressWarnings("unchecked") public void entryDeleted(MapEvent mapEvent) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "{0} received delete event {1}", new Object[] {this, mapEvent}); } // shutdown the subscriber shutdown(State.Removed); // added the removed subscription state to the queue for // internal processing subscriptionUpdates.offer((S) mapEvent.getOldValue()); } }; CacheFactory.getCache(Subscription.CACHENAME).addMapListener(mapListener, subscriptionIdentifier, false); leaseMaintainerThread = new LeaseMaintainerThread(this); leaseMaintainerThread.setDaemon(true); leaseMaintainerThread.setName(String.format("LeaseMaintainerThread[%s]", subscriptionIdentifier.getSubscriberIdentifier())); leaseMaintainerThread.start(); } /** * This verifies that a message from a given publisher was received in order by this subscriber. * * @param message message * @return true if verification succeeds */ boolean verifyMessageSequence(Message message) { long newSequenceNumber = message.getRequestIdentifier().getMessageSequenceNumber(); Identifier publisherIdentifier = message.getRequestIdentifier().getPublisherIdentifier(); Long prevSequenceNumber = prevMessageIdentifierMap.get(publisherIdentifier); if (prevSequenceNumber != null) { prevMessageIdentifierMap.put(publisherIdentifier, newSequenceNumber); // Verify that the new message requestId is greater or equal than the previous message. // It will only be equal in the case of failover where the same message may get delivered twice. if (prevSequenceNumber.longValue() > newSequenceNumber) { // An out-of-order message can happend in the case of a rollback. if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "Message sequence number out of order probably due to a rollback. Prev = {0} New = {1}", new Object[] {prevSequenceNumber, newSequenceNumber}); } } } else { prevMessageIdentifierMap.put(publisherIdentifier, Long.valueOf(newSequenceNumber)); } return true; } /** * Clear the sequence number map for this subscriber. This is done in the case of a rollback. */ void resetMessageSequenceVerification() { prevMessageIdentifierMap = new ConcurrentHashMap(); } /** * {@inheritDoc} */ public MessagingSession getMessagingSession() { return messagingSession; } /** * Returns the {@link State} of the {@link Subscriber}. * * @return return the state */ protected synchronized State getState() { return state; } /** * Returns the current {@link State} of the {@link Subscriber}. * * @param state subscriber state */ protected synchronized void setState(State state) { // ensure to don't try to "reset" from a terminating state if (this.state != State.Removed && this.state != State.Interrupted && this.state != State.Shutdown) { this.state = state; } } /** * Returns the next state update of the {@link Subscription} for this {@link Subscriber}. * If an updated {@link Subscription} state is not available * (ie: has not been sent to the {@link Subscriber}), this method waits until one * arrives (via the {@link MapListener}). * * @return return the subscription object */ protected S getNextSubscriptionUpdate() { try { // get the next available subscription state // (wait's if it has not arrived) S subscription = subscriptionUpdates.take(); // set the state to active setState(State.Active); return subscription; } catch (InterruptedException interruptedException) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "Subscriber was interrupted while waiting for its subscription state to arrive {0}.\n", interruptedException); } // attempt to clean up the subscriber (local resources) shutdown(State.Interrupted); // throw a suitable runtime exception throw new SubscriberInterruptedException(getSubscriptionIdentifier(), "Subscriber was interrupted while waiting for subscription state to arrive", interruptedException); } } /** * Ensures that we have a {@link Subscription} from which we may retrieve {@link Message}s. */ void ensureSubscription() { State state = getState(); if (state == State.Starting) { // wait for the subscription state to arrive getNextSubscriptionUpdate(); } else if (state == State.Removed) { // throw a suitable runtime exception throw new SubscriptionLostException(getSubscriptionIdentifier(), "Subscriber subscription was lost (with expired or the underlying destination has been removed)"); } else if (state == State.Shutdown) { // throw a suitable runtime exception throw new SubscriptionLostException(getSubscriptionIdentifier(), "Attempting to use a Subscription that has been previously released or shutdown"); } else if (state == State.Interrupted) { // throw a suitable runtime exception throw new SubscriptionLostException(getSubscriptionIdentifier(), "Attempting to use a Subscription that was previously interrupted and shutdown"); } } /** * {@inheritDoc} */ public SubscriptionIdentifier getSubscriptionIdentifier() { return subscriptionIdentifier; } /** * {@inheritDoc} */ public Identifier getDestinationIdentifier() { return subscriptionIdentifier.getDestinationIdentifier(); } /** * {@inheritDoc} */ public boolean isActive() { return getState() == State.Starting || getState() == State.Active; } /** * Ensures that the {@link Subscription} for the {@link Subscriber}. * is still valid and that the {@link Subscriber} instance may be used * to receive {@link Message}s */ void ensureActive() { if (!isActive()) { throw new SubscriptionLostException(getSubscriptionIdentifier(), "Attempted to use an inactive Subscriber"); } } /** * Shutdowns the local resources for the {@link Subscriber}. * * @param finalState final state */ protected synchronized void shutdown(State finalState) { if (isActive()) { // clean up the map listener CacheFactory.getCache(Subscription.CACHENAME).removeMapListener(mapListener, subscriptionIdentifier); this.mapListener = null; // we're now shutdown setState(finalState); // shutdown the lease maintainer - it's no longer needed if (leaseMaintainerThread != null && leaseMaintainerThread.isAlive()) { leaseMaintainerThread.terminate(); } } } /** * {@inheritDoc} */ public void unsubscribe() { if (isActive()) { // shutdown local resources for the subscription shutdown(State.Shutdown); // Unsubscribe from the destination. Wait until the subscription is deleted to avoid the bug in INC-795 CacheFactory.getCache(Destination.CACHENAME).invoke(getDestinationIdentifier(), new UnsubscribeProcessor(getSubscriptionIdentifier())); } } /** * {@inheritDoc} */ public boolean isAutoCommitting() { return autocommit; } /** * {@inheritDoc} */ public void setAutoCommit(boolean autoCommit) { if (autoCommit &&!this.autocommit) { this.rollback(); } this.autocommit = autoCommit; } /** * A {@link Thread} that will attempt to continuously maintain * the {@link Lease} for the specified {@link QueueSubscription}. */ static class LeaseMaintainerThread extends Thread { /** * The duration to wait before attempting to refresh the lease for * the {@link Subscriber}. */ private static final long LEASE_REFRESH_TIME_MS = 1000 * 2; // two seconds /** * The {@link Subscriber} for which we are maintaining it's {@link Lease}. */ private Subscriber subscriber; /** * Should the {@link LeaseMaintainerThread} be terminated at the next opportunity? */ private boolean isTerminated; /** * Standard Constructor. * * @param subscriber destination subscriber */ public LeaseMaintainerThread(Subscriber subscriber) { this.subscriber = subscriber; this.isTerminated = false; } /** * Terminate this {@link LeaseMaintainerThread} at the next opportunity. */ public synchronized void terminate() { this.isTerminated = true; } /** * Returns if the {@link LeaseMaintainerThread} should be terminated at the next opportunity. * * @return true if subscription terminated. */ public synchronized boolean isTerminated() { return isTerminated; } /** * {@inheritDoc} */ public void run() { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "LeaseMaintainerThread for {0} commenced", subscriber.getSubscriptionIdentifier()); } while (!isTerminated()) { try { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "LeaseMaintainerThread for {0} sleeping for {1} ms", new Object[] {subscriber.getSubscriptionIdentifier(), LEASE_REFRESH_TIME_MS}); } Thread.sleep(LEASE_REFRESH_TIME_MS); if (isTerminated()) { break; } // Extend the lease. Passing 0 will result in using default value in the LeasedSubscriptionConfiguration CacheFactory.getCache(Subscription.CACHENAME).invoke(subscriber.getSubscriptionIdentifier(), new UpdaterProcessor("extendLease", (long) 0)); // update the lease (using an entry processor) if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "LeaseMaintainerThread for {0} extending current lease", subscriber.getSubscriptionIdentifier()); logger.log(Level.FINER, "LeaseMaintainerThread for {0} has extended the lease", subscriber.getSubscriptionIdentifier()); } } catch (InterruptedException interruptedException) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "LeaseMaintainerThread for {0} interrupted", subscriber.getSubscriptionIdentifier()); } } } if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "LeaseMaintainerThread for {0} completed", subscriber.getSubscriptionIdentifier()); } } } }