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

com.pushtechnology.diffusion.examples.runnable.CompetitiveIncrement Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (C) 2018, 2023 DiffusionData Ltd.
 *
 * 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.pushtechnology.diffusion.examples.runnable;

import static com.pushtechnology.diffusion.client.Diffusion.newTopicSpecification;
import static com.pushtechnology.diffusion.client.features.UpdateConstraint.Operator.IS;
import static com.pushtechnology.diffusion.client.topics.details.TopicType.INT64;

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.features.ClusterRoutingException;
import com.pushtechnology.diffusion.client.features.IncompatibleTopicException;
import com.pushtechnology.diffusion.client.features.IncompatibleTopicStateException;
import com.pushtechnology.diffusion.client.features.NoSuchTopicException;
import com.pushtechnology.diffusion.client.features.TopicCreationResult;
import com.pushtechnology.diffusion.client.features.Topics;
import com.pushtechnology.diffusion.client.features.Topics.ValueStream.Default;
import com.pushtechnology.diffusion.client.features.UnsatisfiedConstraintException;
import com.pushtechnology.diffusion.client.features.UpdateConstraint;
import com.pushtechnology.diffusion.client.features.control.topics.TopicControl.TopicLicenseLimitException;
import com.pushtechnology.diffusion.client.session.PermissionsException;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionClosedException;
import com.pushtechnology.diffusion.client.topics.details.TopicSpecification;

/**
 * A client that increments a topic using stateless operations.
 * 

* The client will increment the topic every 5 seconds. *

* Multiple instances of the client can compete to increment the topic without * generating an incorrect sequence of updates or knowing about other sessions. *

* The client subscribes to the topic to obtain the latest known value. Every * 5 seconds it attempts to increment the topic using the known value as a * constraint. If another session has changed the topic value, the update will * fail and the client will retry with the latest known value. * * @author DiffusionData Limited * @since 6.2 */ public final class CompetitiveIncrement extends AbstractClient { private static final Logger LOG = LoggerFactory .getLogger(CompetitiveIncrement.class); private static final UpdateConstraint.Factory CONSTRAINTS = Diffusion.updateConstraints(); private final ScheduledExecutorService executor = Executors .newSingleThreadScheduledExecutor(); private volatile Future updateTask; private volatile Long value; /** * Constructor. * * @param url The URL to connect to * @param principal The principal to connect as */ public CompetitiveIncrement(String url, String principal) { super(url, principal); } @Override public void onStarted(Session session) { subscribeToPath(session); } private void subscribeToPath(Session session) { // Subscribe to the topic to get the latest value final Topics topics = session.feature(Topics.class); topics .addStream("long/increment", Long.class, new Default() { @Override public void onValue( String topicPath, TopicSpecification specification, Long oldValue, Long newValue) { // Update the latest known value value = newValue; } }); topics .subscribe("long/increment") .whenComplete((result, ex) -> { if (ex != null) { LOG.warn("Subscription failed", ex); stop(); } else { initialiseTopic(topics); } }); } private void initialiseTopic(Topics topics) { // Create the topic and initialise it to 0 if it does not exist topics .addAndSet( "long/increment", newTopicSpecification(INT64), Long.class, 0L, CONSTRAINTS.noTopic()) .whenComplete((result, ex) -> { if (ex != null) { final Throwable cause = ex.getCause(); if (cause instanceof UnsatisfiedConstraintException) { // The constraint was unsatisfied so the topic must // already exist. Begin incrementing the topic value. LOG.info("Topic exists"); scheduleIncrement(topics); } else if (cause instanceof IncompatibleTopicException) { // A topic exists with a different type. This implies // that something else is responsible for the path. LOG.warn("An existing topic is not compatible"); stop(); } else if (cause instanceof IncompatibleTopicStateException) { // A topic exists, managed by a component that does not // allow updates from the API like fanout. Something // else is responsible for the path. LOG.warn("An existing topic is managed by a different component"); stop(); } else if (cause instanceof TopicLicenseLimitException) { // The topic can't be created because the server has // reached the limit it is licensed for. LOG.warn("License limit reached", ex); stop(); } else { LOG.warn("Topic creation failed", ex); stop(); } } else if (result == TopicCreationResult.CREATED) { // Begin incrementing the topic value. LOG.info("Topic created"); scheduleIncrement(topics); } }); } /** * Schedule an increment in 5 seconds. */ private void scheduleIncrement(Topics topics) { updateTask = executor.schedule( () -> performIncrement(topics), 5L, TimeUnit.SECONDS); } /** * Perform an increment of the latest known value. */ private void performIncrement(Topics topics) { final Long currentValue = value; topics .set( "long/increment", Long.class, currentValue + 1, CONSTRAINTS.value(IS, currentValue)) .whenComplete((result, ex) -> { if (ex != null) { // Handle any failure handleIncrementFailure(topics, ex); } else { // On success schedule the next update LOG.info("Topic incremented {} -> {}", currentValue, currentValue + 1); scheduleIncrement(topics); } }); } private void handleIncrementFailure(Topics topics, Throwable ex) { final Throwable cause = ex.getCause(); if (cause instanceof ClusterRoutingException || cause instanceof UnsatisfiedConstraintException) { // A transient cluster failure occurred during the increment // or the constraint was not satisfied, another session must // have updated the topic. // Retry incrementing the topic until successful. performIncrement(topics); } else if (cause instanceof NoSuchTopicException) { // The topic has been removed since it was created. This could be // recoverable, but it implies that something else has taken // responsibility for the path. Attempting to recover could cause // this and something else to compete over the topic. LOG.warn("The topic has been deleted"); stop(); } else if (cause instanceof IncompatibleTopicException) { // The topic has been replaced with a topic of a different type // since it was created. This implies that something else has taken // responsibility for the path. LOG.warn("The topic is not compatible"); stop(); } else if (cause instanceof IncompatibleTopicStateException) { // The topic is managed by a component that does not allow updates // from the API like fanout. Something else has taken // responsibility for the path. LOG.warn("The topic is managed by a different component"); stop(); } else if (cause instanceof PermissionsException) { // The session does't have permission update update the path. // This is not recoverable. LOG.warn("The session does't have permission to update the path"); stop(); } else if (cause instanceof SessionClosedException) { // The session has closed. This is not recoverable. LOG.warn("The session has closed"); stop(); } } @Override public void onDisconnected() { final Future task = this.updateTask; if (task != null) { task.cancel(false); } } /** * Entry point for the example. * @param args The command line arguments * @throws InterruptedException If the main thread was interrupted */ public static void main(String[] args) throws InterruptedException { final CompetitiveIncrement client = new CompetitiveIncrement("ws://diffusion.example.com:80", "auth"); client.start("auth_secret"); client.waitForStopped(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy