From ea204deed6afa7fde4db1ec2e35290e31b137ec3 Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Fri, 10 Jan 2025 09:46:36 -0500 Subject: [PATCH] Enh 37467440 - [37387100->25.03] Support the Coherence Topics API over Extend (merge main -> ce/main 113319) [git-p4: depot-paths = "//dev/coherence-ce/main/": change = 113355] --- prj/coherence-core-components/pom.xml | 4 + .../net/extend/RemoteNamedTopic.java | 502 +++++ .../component/net/extend/RemotePublisher.java | 259 +++ .../net/extend/RemoteSubscriber.java | 261 +++ .../net/extend/RemoteSubscriberChannel.java | 280 +++ .../net/extend/RequestSendingProxy.java | 75 + .../message/request/NamedTopicRequest.java | 103 + .../request/TopicPublisherRequest.java | 54 + .../extend/message/request/TopicRequest.java | 42 + .../message/request/TopicServiceRequest.java | 194 ++ .../request/TopicSubscriberRequest.java | 57 + .../BaseTopicMessageFactory.java | 137 ++ .../messageFactory/NamedTopicFactory.java | 1831 +++++++++++++++++ .../messageFactory/TopicServiceFactory.java | 810 ++++++++ .../extend/protocol/NamedTopicProtocol.java | 160 ++ .../extend/protocol/TopicServiceProtocol.java | 113 + .../net/extend/proxy/NamedTopicProxy.java | 447 ++++ .../net/extend/proxy/TopicPublisherProxy.java | 547 +++++ .../extend/proxy/TopicSubscriberProxy.java | 382 ++++ .../proxy/serviceProxy/TopicServiceProxy.java | 719 +++++++ .../remoteService/RemoteTopicService.java | 603 ++++++ .../coherence/component/util/SafeCluster.java | 31 +- .../daemon/queueProcessor/service/Peer.java | 14 +- .../service/grid/ProxyService.java | 116 +- .../SafeSimpleNamedTopic.java | 54 + .../SafeSimpleTopicService.java | 556 +++++ .../config/scheme/AbstractCachingScheme.java | 4 +- .../config/scheme/BaseGrpcTopicScheme.java | 118 ++ .../config/scheme/RemoteTopicScheme.java | 175 ++ .../coherence/config/scheme/TopicScheme.java | 16 +- .../xml/CacheConfigNamespaceHandler.java | 16 +- .../DefaultRemoteGrpcServiceDependencies.java | 27 +- ...ultRemoteGrpcTopicServiceDependencies.java | 47 + .../RemoteGrpcTopicServiceDependencies.java | 18 + .../net/service/LegacyXmlServiceHelper.java | 8 +- .../DefaultTopicServiceProxyDependencies.java | 81 + .../LegacyXmlTopicServiceProxyHelper.java | 50 + .../proxy/TopicServiceProxyDependencies.java | 27 + ...DefaultRemoteTopicServiceDependencies.java | 67 + .../LegacyXmlRemoteTopicServiceHelper.java | 39 + .../RemoteTopicServiceDependencies.java | 18 + .../grid/DefaultProxyServiceDependencies.java | 39 +- .../grid/ProxyServiceDependencies.java | 13 +- .../net/topic/BaseRemotePublisher.java | 397 ++++ .../net/topic/BaseRemoteSubscriber.java | 396 ++++ .../topic/DefaultTopicBackingMapManager.java | 110 + .../java/com/tangosol/net/CacheService.java | 4 +- .../com/tangosol/net/ClusterDependencies.java | 38 +- .../ExtensibleConfigurableCacheFactory.java | 11 +- .../tangosol/util/ExternalizableHelper.java | 21 +- .../main/resources/coherence-cache-config.xsd | 76 +- .../defaults/coherence-cache-config.xml | 73 +- .../src/main/resources/tangosol-coherence.xml | 44 +- .../client/common/config/GrpcCacheScheme.java | 14 +- .../java/topics/AbstractNamedTopicTests.java | 14 +- .../AbstractTopicsStorageRecoveryTests.java | 419 +--- .../java/topics/callables/DestroyTopic.java | 58 + .../java/topics/callables/UsingTopic.java | 38 + .../topics/proxy/TopicServiceProxyTests.java | 139 ++ .../main/java/topics/remote/EnsureTopic.java | 70 + ...faultConfigJavaClientJavaClusterTests.java | 163 ++ ...efaultConfigJavaClientPofClusterTests.java | 175 ++ ...efaultConfigPofClientJavaClusterTests.java | 163 ++ ...DefaultConfigPofClientPofClusterTests.java | 165 ++ .../remote/RemoteSubscriberFailureTests.java | 67 + .../remote/RemoteTopicChannelCountTests.java | 35 + .../RemoteTopicStorageRecoveryTests.java | 57 + .../java/topics/remote/RemoteTopicTests.java | 242 +++ .../main/resources/client-cache-config.xml | 63 +- .../coherence-cache-config-override.xml | 19 + .../simple-persistence-bdb-cache-config.xml | 47 +- .../simple-persistence-bdb-client-config.xml | 73 + .../resources/topics-calculator-config.xml | 30 +- .../topics-channel-client-config.xml | 43 + .../main/resources/topics-channel-config.xml | 30 +- .../resources/topics-small-page-config.xml | 30 +- .../PagedTopicSchemeProcessorTest.java | 34 +- 77 files changed, 12024 insertions(+), 448 deletions(-) create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteNamedTopic.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemotePublisher.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriber.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriberChannel.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RequestSendingProxy.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/NamedTopicRequest.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicPublisherRequest.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicRequest.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicServiceRequest.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicSubscriberRequest.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/BaseTopicMessageFactory.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/NamedTopicFactory.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/TopicServiceFactory.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/NamedTopicProtocol.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/TopicServiceProtocol.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/NamedTopicProxy.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicPublisherProxy.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicSubscriberProxy.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/serviceProxy/TopicServiceProxy.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/remoteService/RemoteTopicService.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleNamedTopic.java create mode 100644 prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleTopicService.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BaseGrpcTopicScheme.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteTopicScheme.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcTopicServiceDependencies.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/RemoteGrpcTopicServiceDependencies.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/DefaultTopicServiceProxyDependencies.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/LegacyXmlTopicServiceProxyHelper.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/TopicServiceProxyDependencies.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/DefaultRemoteTopicServiceDependencies.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/LegacyXmlRemoteTopicServiceHelper.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/RemoteTopicServiceDependencies.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemotePublisher.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemoteSubscriber.java create mode 100644 prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/DefaultTopicBackingMapManager.java create mode 100644 prj/test/functional/topics/src/main/java/topics/callables/DestroyTopic.java create mode 100644 prj/test/functional/topics/src/main/java/topics/callables/UsingTopic.java create mode 100644 prj/test/functional/topics/src/main/java/topics/proxy/TopicServiceProxyTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/EnsureTopic.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteDefaultConfigJavaClientJavaClusterTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteDefaultConfigJavaClientPofClusterTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteDefaultConfigPofClientJavaClusterTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteDefaultConfigPofClientPofClusterTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteSubscriberFailureTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteTopicChannelCountTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteTopicStorageRecoveryTests.java create mode 100644 prj/test/functional/topics/src/main/java/topics/remote/RemoteTopicTests.java create mode 100644 prj/test/functional/topics/src/main/resources/coherence-cache-config-override.xml create mode 100644 prj/test/functional/topics/src/main/resources/simple-persistence-bdb-client-config.xml create mode 100644 prj/test/functional/topics/src/main/resources/topics-channel-client-config.xml diff --git a/prj/coherence-core-components/pom.xml b/prj/coherence-core-components/pom.xml index 43745978557ea..d47b7465330a6 100644 --- a/prj/coherence-core-components/pom.xml +++ b/prj/coherence-core-components/pom.xml @@ -23,6 +23,10 @@ jar + + false + + ${coherence.group.id} diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteNamedTopic.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteNamedTopic.java new file mode 100644 index 0000000000000..204b13e920881 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteNamedTopic.java @@ -0,0 +1,502 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.RemoteNamedTopic + +package com.tangosol.coherence.component.net.extend; + +import com.tangosol.coherence.Component; + +import com.tangosol.coherence.component.net.Extend; + +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; + +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; + +import com.tangosol.coherence.component.net.extend.remoteService.RemoteTopicService; + +import com.tangosol.coherence.component.util.daemon.QueueProcessor; + +import com.tangosol.internal.net.topic.NamedTopicPublisher; +import com.tangosol.internal.net.topic.NamedTopicSubscriber; + +import com.tangosol.internal.net.topic.PublisherConnector; +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; + +import com.tangosol.net.events.internal.TopicDispatcher; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Connection; +import com.tangosol.net.messaging.ConnectionException; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.NamedTopicEvent; +import com.tangosol.net.topic.NamedTopicListener; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; + +import com.tangosol.util.Filter; +import com.tangosol.util.Listeners; +import com.tangosol.util.ValueExtractor; + +import java.util.Set; + +import java.util.stream.Collectors; + +/** + * An Extend {@link Channel.Receiver} for a remote topic. + * + * @author Jonathan Knight 2024.11.26 + */ +@SuppressWarnings("rawtypes") +public class RemoteNamedTopic + extends Extend + implements Channel.Receiver, NamedTopic, SubscriberConnector.Factory, PublisherConnector.Factory + { + /** + * Property Channel + */ + private Channel __m_Channel; + + /** + * Property DeactivationListeners + */ + private Listeners __m_DeactivationListeners; + + /** + * Property EventDispatcher + */ + private QueueProcessor __m_EventDispatcher; + + /** + * Property Listeners + */ + private Listeners __m_Listeners; + + /** + * Property Released + *

+ * A flag indicating whether this topic has been released. + */ + private boolean __m_Released; + + /** + * Property TopicName + */ + private String __m_TopicName; + + /** + * Property TopicService + */ + private RemoteTopicService __m_TopicService; + + /** + * The topic lifecycle dispatcher. + */ + private TopicDispatcher __m_TopicLifecycleEventDispatcher; + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor. + */ + public RemoteNamedTopic() + { + this(null, null, true); + } + + /** + * Initializing constructor. + */ + public RemoteNamedTopic(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setListeners(new com.tangosol.util.Listeners()); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + @Override + public void addListener(NamedTopicListener listener) + { + getListeners().add(listener); + } + + @Override + public RemotePublisher createPublisherConnector(Publisher.Option[] options) + { + RemoteTopicService service = (RemoteTopicService) getService(); + return service.createPublisherConnector(getTopicName(), options); + } + + @Override + @SuppressWarnings("unchecked") + public Publisher createPublisher(Publisher.Option[] options) + { + RemotePublisher connector = createPublisherConnector(options); + return new NamedTopicPublisher<>(this, connector, options); + } + + @Override + public RemoteSubscriber createSubscriberConnector(Subscriber.Option[] options) + { + RemoteTopicService service = (RemoteTopicService) getService(); + return service.createSubscriberConnector(getTopicName(), options); + } + + @Override + public final Subscriber createSubscriber(Subscriber.Option[] options) + { + RemoteSubscriber connector = createSubscriberConnector(options); + return new NamedTopicSubscriber<>(this, connector, options); + } + + @Override + public void destroy() + { + getTopicService().destroyTopic(this); + } + + @Override + public void destroySubscriberGroup(String sGroupName) + { + Channel channel = ensureChannel(); + NamedTopicFactory.DestroySubscriberGroupRequest request = (NamedTopicFactory.DestroySubscriberGroupRequest) + channel.getMessageFactory().createMessage(NamedTopicFactory.TYPE_ID_DESTROY_SUBSCRIBER_GROUP); + + request.setSubscriberGroup(sGroupName); + channel.request(request); + } + + @Override + public void ensureSubscriberGroup(String sGroupName, Filter filter, ValueExtractor extractor) + { + Channel channel = ensureChannel(); + NamedTopicFactory.EnsureSubscriberGroupRequest request = (NamedTopicFactory.EnsureSubscriberGroupRequest) + channel.getMessageFactory().createMessage(NamedTopicFactory.TYPE_ID_ENSURE_SUBSCRIBER_GROUP); + + request.setSubscriberGroup(sGroupName); + request.setFilter(filter); + request.setExtractor(extractor); + channel.request(request); + } + + /** + * Getter for property Channel.

+ */ + public Channel getChannel() + { + return __m_Channel; + } + + /** + * Return the Channel used by this remote topic. If the Channel is null + * or is not open, this method throws an exception. + * + * @return a Channel that can be used to exchange Messages with the + * remote ProxyService + */ + protected Channel ensureChannel() + { + Channel channel = getChannel(); + if (channel == null || !channel.isOpen()) + { + String sCause = "released"; + Connection connection = null; + + if (channel != null) + { + connection = channel.getConnection(); + if (connection == null || !connection.isOpen()) + { + sCause = "closed"; + } + } + + throw new ConnectionException("NamedTopic \"" + + getTopicName() + "\" has been " + sCause, + connection); + } + + return channel; + } + + @Override + public int getChannelCount() + { + return getTopicService().getChannelCount(getTopicName()); + } + + /** + * Getter for property DeactivationListeners.

+ */ + public Listeners getDeactivationListeners() + { + return __m_DeactivationListeners; + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + return "NamedTopic=" + getTopicName() + + ", Service=" + getTopicService().getInfo().getServiceName(); + } + + /** + * Getter for property EventDispatcher.

+ */ + public QueueProcessor getEventDispatcher() + { + return __m_EventDispatcher; + } + + /** + * Getter for property Listeners.

+ */ + public Listeners getListeners() + { + return __m_Listeners; + } + + @Override + public String getName() + { + return getTopicName(); + } + + @Override + public Protocol getProtocol() + { + return NamedTopicProtocol.getInstance(); + } + + @Override + public int getRemainingMessages(String sSubscriberGroup, int[] anChannel) + { + Channel channel = ensureChannel(); + NamedTopicFactory.GetRemainingMessagesRequest request = (NamedTopicFactory.GetRemainingMessagesRequest) + channel.getMessageFactory().createMessage(NamedTopicFactory.TYPE_ID_REMAINING_MESSAGES); + + request.setSubscriberGroup(sSubscriberGroup); + request.setChannels(anChannel); + + return (Integer) channel.request(request); + } + + @Override + public com.tangosol.net.Service getService() + { + return getTopicService(); + } + + @Override + public Set getSubscriberGroups() + { + return getTopicService().getSubscriberGroups(getTopicName()) + .stream() + .filter(SubscriberGroupId::isDurable) + .map(SubscriberGroupId::getGroupName) + .collect(Collectors.toSet()); + } + + /** + * Getter for property TopicName.

+ */ + public String getTopicName() + { + return __m_TopicName; + } + + /** + * Getter for property TopicService.

+ */ + @Override + public RemoteTopicService getTopicService() + { + return __m_TopicService; + } + + @Override + public boolean isActive() + { + Channel channel = getChannel(); + return channel != null && channel.isOpen(); + } + + /** + * Getter for property Destroyed.

+ * A flag indicating whether this topic has been destroyed. + */ + @Override + public boolean isDestroyed() + { + Channel channel = getChannel(); + Connection connection = channel == null ? null : channel.getConnection(); + + if (channel == null || connection == null) + { + // unknown if destroyed or not. + return false; + } + else + { + // infer destroyed when channel is closed and connection is open. + return !channel.isOpen() && connection.isOpen(); + } + } + + @Override + public void onChannelClosed(Channel channel) + { + Listeners listeners = getListeners(); + if (!listeners.isEmpty()) + { + NamedTopicEvent evt = new NamedTopicEvent(this, NamedTopicEvent.Type.Destroyed); + evt.dispatch(listeners); + } + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + message.run(); + } + + @Override + public void registerChannel(Channel channel) + { + setChannel(channel); + } + + @Override + public boolean isReleased() + { + return __m_Released; + } + + @Override + public void release() + { + setReleased(true); + getTopicService().releaseTopic(this); + } + + @Override + public void removeListener(NamedTopicListener listener) + { + getListeners().add(listener); + } + + @Override + public void unregisterChannel(Channel channel) + { + setChannel(null); + } + + /** + * Setter for property Channel.

+ */ + public void setChannel(Channel channel) + { + __m_Channel = channel; + } + + /** + * Setter for property DeactivationListeners.

+ */ + public void setDeactivationListeners(Listeners listenersDeactivation) + { + __m_DeactivationListeners = listenersDeactivation; + } + + /** + * Setter for property EventDispatcher.

+ */ + public void setEventDispatcher(QueueProcessor processorDispatcher) + { + __m_EventDispatcher = processorDispatcher; + } + + /** + * Setter for property Listeners.

+ */ + public void setListeners(Listeners listeners) + { + __m_Listeners = listeners; + } + + /** + * Setter for property Released.

+ * A flag indicating whether this topic has been released. + */ + public void setReleased(boolean fReleased) + { + __m_Released = fReleased; + } + + /** + * Setter for property TopicName.

+ */ + public void setTopicName(String sName) + { + __m_TopicName = sName; + } + + /** + * Setter for property TopicService.

+ */ + public void setTopicService(RemoteTopicService serviceTopic) + { + __m_TopicService = serviceTopic; + } + + public TopicDispatcher getTopicLifecycleEventDispatcher() + { + return __m_TopicLifecycleEventDispatcher; + } + + public void setTopicLifecycleEventDispatcher(TopicDispatcher dispatcher) + { + __m_TopicLifecycleEventDispatcher = dispatcher; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemotePublisher.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemotePublisher.java new file mode 100644 index 0000000000000..b55cf0ea3d85c --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemotePublisher.java @@ -0,0 +1,259 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.RemoteNamedTopic + +package com.tangosol.coherence.component.net.extend; + +import com.oracle.coherence.common.base.Logger; + +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; + +import com.tangosol.internal.net.topic.BaseRemotePublisher; +import com.tangosol.internal.net.topic.NamedTopicPublisher.PublisherEvent; +import com.tangosol.internal.net.topic.PublishResult; +import com.tangosol.internal.net.topic.PublisherChannelConnector; +import com.tangosol.internal.net.topic.PublisherConnector; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Connection; +import com.tangosol.net.messaging.ConnectionException; + +import com.tangosol.net.topic.Publisher; + +import com.tangosol.util.Binary; + +import java.util.List; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** + * Base component for all Coherence*Extend implementation components. + * + * @author Jonathan Knight 2024.11.26 + */ +public class RemotePublisher + extends BaseRemotePublisher + implements Channel.Receiver, PublisherConnector + { + // ----- constructors --------------------------------------------------- + + /** + * Create a {@link RemotePublisher}. + * + * @param nId the unique publisher identifier + * @param cChannel the default channel count for the topic + * @param options the publisher options + */ + public RemotePublisher(long nId, int cChannel, Publisher.Option[] options) + { + super(nId, cChannel, options); + } + + // ----- ConnectedPublisher.Connector methods --------------------------- + + @Override + public boolean isActive() + { + Channel channel = m_channel; + return channel != null && channel.isOpen(); + } + + @Override + public void close() + { + super.close(); + try + { + // when this is called due to certain connection error, e.g. ping + // timeout, the channel could be null and closed. + Channel channel = getChannel(); + if (channel != null) + { + channel.close(); + } + } + catch (RuntimeException e) + { + // ignored + } + } + + @Override + public void ensureConnected() + { + ensureChannel(); + } + + @Override + public PublisherChannelConnector createChannelConnector(int nChannel) + { + return new ChannelConnector(getId(), nChannel); + } + + @Override + public void unregisterChannel(Channel channel) + { + if (m_channel != null) + { + Logger.finer("Disconnected remote publisher topic=" + getTopicName() + " id=" + getId()); + dispatchEvent(PublisherEvent.Type.Disconnected); + m_channel = null; + } + } + + @Override + public String getName() + { + return getTopicName(); + } + + @Override + public Protocol getProtocol() + { + return NamedTopicProtocol.getInstance(); + } + + @Override + public void onChannelClosed(Channel channel) + { + dispatchEvent(PublisherEvent.Type.Disconnected); + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + message.run(); + } + + @Override + public void registerChannel(Channel channel) + { + m_channel = channel; + dispatchEvent(PublisherEvent.Type.Connected); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Getter for property Channel.

+ */ + public Channel getChannel() + { + return m_channel; + } + + /** + * Return the Channel used by this remote topic. If the Channel is null + * or is not open, this method throws an exception. + * + * @return a Channel that can be used to exchange Messages with the + * remote ProxyService + */ + protected Channel ensureChannel() + { + Channel channel = m_channel; + if (channel == null || !channel.isOpen()) + { + String sCause = "released"; + Connection connection = null; + + if (channel != null) + { + connection = channel.getConnection(); + if (connection == null || !connection.isOpen()) + { + sCause = "closed"; + } + } + + throw new ConnectionException("NamedTopic \"" + + getTopicName() + "\" has been " + sCause, + connection); + } + return channel; + } + + /** + * Setter for property Channel.

+ */ + public void setChannel(Channel channel) + { + m_channel = channel; + } + + // ----- inner class: ChannelConnector ---------------------------------- + + /** + * The publisher channel connector. + */ + protected class ChannelConnector + extends BaseChannelConnector + { + /** + * Create a {@link ChannelConnector}. + * + * @param nId the unique identifier for this publisher + * @param nChannel the channel identifier for this connector + */ + public ChannelConnector(long nId, int nChannel) + { + super(nId, nChannel); + } + + @Override + public boolean isActive() + { + return RemotePublisher.this.isActive(); + } + + @Override + protected CompletionStage offerInternal(List listBinary, int nNotifyPostFull) + { + try + { + Channel channel = ensureChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + NamedTopicFactory.PublishRequest request = (NamedTopicFactory.PublishRequest) factory.createMessage(NamedTopicFactory.TYPE_ID_PUBLISH); + + request.setChannel(f_nChannel); + request.setBinaries(listBinary); + request.setNotify(nNotifyPostFull); + + PublishResult result = (PublishResult) channel.request(request); + return CompletableFuture.completedFuture(result); + } + catch (Throwable t) + { + if (t instanceof ConnectionException) + { + Channel channel = m_channel; + if (channel != null) + { + m_channel = null; + Logger.finer("Closing remote publisher channel due to connection exception " + m_channel); + // The proxy disconnected, close the channel + dispatchEvent(PublisherEvent.Type.Disconnected); + channel.close(); + } + } + return CompletableFuture.failedFuture(t); + } + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The {@link Channel} to use to send and receive requests and messages. + */ + private Channel m_channel; + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriber.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriber.java new file mode 100644 index 0000000000000..32d0f9661d94c --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriber.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend; + +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; + +import com.tangosol.internal.net.topic.BaseRemoteSubscriber; +import com.tangosol.internal.net.topic.SeekResult; +import com.tangosol.internal.net.topic.SimpleReceiveResult; +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.internal.net.topic.TopicSubscription; + +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberId; + +import com.tangosol.net.messaging.Channel; + +import com.tangosol.net.topic.Position; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.net.topic.Subscriber.Element; +import com.tangosol.net.topic.TopicDependencies; + +import java.time.Instant; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +/** + * A client side remote {@link SubscriberConnector}. + * + * @param the type of element the subscriber receives + * + * @author Jonathan Knight 2024.11.26 + */ +public class RemoteSubscriber + extends BaseRemoteSubscriber + implements SubscriberConnector + { + /** + * Create a {@link BaseRemoteSubscriber}. + * + * @param sTopicName the topic name + * @param remoteChannel the channel to connect to the proxy server + * @param subscriberId the subscriber identifier + * @param groupId the subscriber group identifier + */ + public RemoteSubscriber(String sTopicName, RemoteSubscriberChannel remoteChannel, + SubscriberId subscriberId, SubscriberGroupId groupId) + { + super(sTopicName, subscriberId, groupId); + f_remoteChannel = Objects.requireNonNull(remoteChannel); + remoteChannel.setSubscriber(this); + } + + @Override + public boolean isActive() + { + Channel channel = f_remoteChannel.getChannel(); + return channel != null && channel.isOpen(); + } + + @Override + public void ensureConnected() + { + f_remoteChannel.ensureChannel(); + } + + @Override + public Position[] initialize(ConnectedSubscriber subscriber, boolean fForceReconnect, boolean fReconnect, boolean fDisconnected) + { + Object[] aoResult = f_remoteChannel.send(NamedTopicFactory.TYPE_ID_INITIALIZE_SUBSCRIPTION, NamedTopicFactory.InitializeSubscriptionRequest.class, + request -> + { + request.setForceReconnect(fForceReconnect); + request.setReconnect(fReconnect); + request.setDisconnected(fDisconnected); + }); + m_subscriptionId = (Long) aoResult[0]; + m_connectionTimestamp = (Long) aoResult[1]; + Object[] aoPosition = (Object[]) aoResult[2]; + Position[] aHead = new Position[aoPosition.length]; + for (int i = 0; i < aoPosition.length; i++) + { + aHead[i] = (Position) aoPosition[i]; + } + return aHead; + } + + @Override + public boolean ensureSubscription(ConnectedSubscriber subscriber, long subscriptionId, boolean fForceReconnect) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_ENSURE_SUBSCRIPTION, NamedTopicFactory.EnsureSubscriptionRequest.class, + request -> + { + request.setSubscriptionId(subscriptionId); + request.setForceReconnect(fForceReconnect); + }); + } + + @Override + public TopicSubscription getSubscription(ConnectedSubscriber subscriber, long id) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_GET_SUBSCRIPTION, NamedTopicFactory.GetSubscriptionRequest.class, + request -> request.setSubscriptionId(id)); + } + + @Override + public int getRemainingMessages(SubscriberGroupId groupId, int... anChannel) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_REMAINING_MESSAGES, NamedTopicFactory.GetRemainingMessagesRequest.class, + request -> + { + request.setSubscriberGroup(groupId.getGroupName()); + request.setChannels(anChannel); + }); + } + + @Override + public SortedSet getOwnedChannels(ConnectedSubscriber subscriber) + { + Collection col = f_remoteChannel.send(NamedTopicFactory.TYPE_ID_GET_OWNED_CHANNELS); + return new TreeSet<>(col); + } + + @Override + protected SimpleReceiveResult receiveInternal(int nChannel, Position headPosition, long lVersion) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_RECEIVE, NamedTopicFactory.ReceiveRequest.class, request -> + { + request.setChannel(nChannel); + request.setPosition(headPosition); + request.setVersion(lVersion); + }); + } + + @Override + public Element peek(int nChannel, Position position) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_PEEK, NamedTopicFactory.PeekRequest.class, request -> + { + request.setChannel(nChannel); + request.setPosition(position); + }); + } + + @Override + protected void commitInternal(int nChannel, Position position, CommitHandler handler) + { + Object[] aoResult = f_remoteChannel.send(NamedTopicFactory.TYPE_ID_COMMIT, NamedTopicFactory.CommitRequest.class, request -> + { + request.setChannel(nChannel); + request.setPosition(position); + }); + + Subscriber.CommitResult result = (Subscriber.CommitResult) aoResult[NamedTopicFactory.CommitRequest.RESPONSE_ID_RESULT]; + Position head = (Position) aoResult[NamedTopicFactory.CommitRequest.RESPONSE_ID_HEAD]; + handler.committed(result, head); + } + + @Override + public boolean isCommitted(SubscriberGroupId groupId, int nChannel, Position position) + { + f_remoteChannel.ensureChannel(); + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_IS_COMMITTED, NamedTopicFactory.IsCommitedRequest.class, request -> + { + request.setChannel(nChannel); + request.setPosition(position); + }); + } + + @Override + public Map getLastCommittedInGroup(SubscriberGroupId groupId) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_GET_LAST_COMMITTED); + } + + @Override + public Map getTopicHeads(int[] anChannel) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_GET_HEADS, NamedTopicFactory.GetHeadsRequest.class, request -> + { + request.setChannels(anChannel); + }); + } + + @Override + public Map getTopicTails() + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_GET_TAILS); + } + + @Override + public Map seekToPosition(ConnectedSubscriber subscriber, Map map) + { + List> list; + list = f_remoteChannel.send(NamedTopicFactory.TYPE_ID_SEEK, NamedTopicFactory.SeekRequest.class, request -> + { + request.setPositions(map); + }); + return list.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public Map seekToTimestamp(ConnectedSubscriber subscriber, Map map) + { + return f_remoteChannel.send(NamedTopicFactory.TYPE_ID_SEEK, NamedTopicFactory.SeekRequest.class, request -> + { + request.setTimestamps(map); + }); + } + + @Override + protected void sendHeartbeat(boolean fAsync) + { + f_remoteChannel.send(NamedTopicFactory.TYPE_ID_HEARTBEAT, NamedTopicFactory.HeartbeatRequest.class, + request -> request.setAsync(fAsync)); + } + + @Override + public TopicDependencies getTopicDependencies() + { + return f_remoteChannel.getTopicService().getTopicBackingMapManager() + .getTopicDependencies(f_sTopicName); + } + + @Override + public void closeSubscription(ConnectedSubscriber subscriber, boolean fDestroyed) + { + try + { + // when this is called due to certain connection error, e.g. ping + // timeout, the channel could be null and closed. + com.tangosol.net.messaging.Channel channel = f_remoteChannel.getChannel(); + if (channel != null) + { + channel.close(); + } + } + catch (RuntimeException e) + { + // ignored + } + } + + // ----- data members --------------------------------------------------- + + /** + * The channel to connect to the proxy server. + */ + private final RemoteSubscriberChannel f_remoteChannel; + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriberChannel.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriberChannel.java new file mode 100644 index 0000000000000..fc03e4a9c0a57 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RemoteSubscriberChannel.java @@ -0,0 +1,280 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.RemoteNamedTopic + +package com.tangosol.coherence.component.net.extend; + +import com.oracle.coherence.common.base.Exceptions; +import com.oracle.coherence.common.base.Logger; +import com.tangosol.coherence.component.net.Extend; +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.internal.net.topic.SubscriberConnector.SubscriberEvent; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberId; +import com.tangosol.net.TopicService; +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Connection; +import com.tangosol.net.messaging.ConnectionException; + +import java.util.function.Consumer; + +/** + * A remote topic Extend channel receiver. + * + * @author Jonathan Knight 2024.11.26 + */ +public class RemoteSubscriberChannel + extends Extend + implements Channel.Receiver + { + /** + * Property Channel + */ + private Channel m_channel; + + /** + * The name of the topic. + */ + private String m_sTopicName; + + /** + * The subscriber identifier. + */ + private SubscriberId m_subscriberId; + + /** + * Property TopicService + */ + private TopicService m_topicService; + + /** + * The subscriber connected to this connector. + */ + private RemoteSubscriber m_subscriber; + + // ----- constructors --------------------------------------------------- + + public RemoteSubscriberChannel() + { + super(null, null, true); + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // state initialization: public and protected properties + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Send a request. + * + * @param id the type identifier of the message to send + * @param the expected result type + * + * @return the result of sending the request + */ + R send(int id) + { + return send(id, com.tangosol.net.messaging.Request.class, request -> {}); + } + + /** + * Send a request. + * + * @param id the type identifier of the message to send + * @param clz the type of the request + * @param configurer a {@link Consumer} that may configure the request + * @param the expected result type + * @param the type of the request + * + * @return the result of sending the request + */ + @SuppressWarnings("unchecked") + R send(int id, Class clz, Consumer configurer) + { + Channel channel = ensureChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + M request = (M) factory.createMessage(id); + configurer.accept(request); +// try +// { + return (R) channel.request(request); +// } +// catch (Exception e) +// { +// if (e instanceof ConnectionException) +// { +// channel.close(); +// } +// throw Exceptions.ensureRuntimeException(e); +// } + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + return "Subscriber=" + m_subscriberId + + ", Subscriber=" + m_sTopicName + + ", Service=" + m_topicService.getInfo().getServiceName(); + } + + @Override + public void unregisterChannel(Channel channel) + { + if (m_channel != null) + { + Logger.finer("Disconnected remote subscriber topic=" + m_sTopicName + " id=" + m_subscriber.getSubscriberId()); + m_subscriber.onDisconnected(); + m_channel = null; + } + } + + @Override + public String getName() + { + return "Subscriber:" + m_sTopicName; + } + + @Override + public Protocol getProtocol() + { + return NamedTopicProtocol.getInstance(); + } + + @Override + public void onChannelClosed(Channel channel) + { + Logger.finer("Disconnected remote subscriber due to channel closure topic=" + m_sTopicName + " id=" + m_subscriber.getSubscriberId()); + m_subscriber.onDisconnected(); + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + message.run(); + } + + @Override + public void registerChannel(Channel channel) + { + setChannel(channel); + } + + public boolean isDestroyed() + { + Channel channel = getChannel(); + Connection connection = channel == null ? null : channel.getConnection(); + + if (channel == null || connection == null) + { + // unknown if destroyed or not. + return false; + } + else + { + // infer destroyed when channel is closed and connection is open. + return !channel.isOpen() && connection.isOpen(); + } + } + + /** + * Getter for property Channel.

+ */ + public Channel getChannel() + { + return m_channel; + } + + /** + * Return the Channel used by this remote topic. If the Channel is null + * or is not open, this method throws an exception. + * + * @return a Channel that can be used to exchange Messages with the + * remote ProxyService + */ + protected Channel ensureChannel() + { + Channel channel = getChannel(); + if (channel == null || !channel.isOpen()) + { + String sCause = "released"; + Connection connection = null; + + if (channel != null) + { + connection = channel.getConnection(); + if (connection == null || !connection.isOpen()) + { + sCause = "closed"; + } + } + + throw new ConnectionException("NamedTopic \"" + + m_sTopicName + "\" has been " + sCause, + connection); + } + + return channel; + } + + /** + * Set the Extend {@link Channel}. + */ + public void setChannel(Channel channel) + { + m_channel = channel; + } + + public void setSubscriberId(SubscriberId subscriberId) + { + m_subscriberId = subscriberId; + } + + public void setTopicService(TopicService serviceTopic) + { + m_topicService = serviceTopic; + } + + public TopicService getTopicService() + { + return m_topicService; + } + + public void setTopicName(String sTopicName) + { + m_sTopicName = sTopicName; + } + + public RemoteSubscriber getSubscriber() + { + return m_subscriber; + } + + public void setSubscriber(RemoteSubscriber subscriber) + { + m_subscriber = subscriber; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RequestSendingProxy.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RequestSendingProxy.java new file mode 100644 index 0000000000000..cb92c4cce3666 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/RequestSendingProxy.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Request; + +/** + * A class that can use a {@link RequestSender} to + * send a {@link Request}. + * + * @author Jonathan Knight 2024.11.26 + */ +public interface RequestSendingProxy + { + /** + * Return the {@link Channel} the proxy is using. + * + * @return the {@link Channel} the proxy is using + */ + Channel getChannel(); + + /** + * Obtain the {@link MessageFactory} to use to create + * messages and requests. + * + * @return the {@link MessageFactory} to use to create + * messages and requests + */ + MessageFactory getMessageFactory(); + + /** + * Set the {@link RequestSender} for this receiver. + * + * @param sender the {@link RequestSender} for this receiver + */ + void setRequestSender(RequestSender sender); + + /** + * Return the {@link RequestSender} for this receiver. + * + * @return the {@link RequestSender} for this receiver + */ + RequestSender getRequestSender(); + + // ----- inner interface: RequestSender --------------------------------- + + /** + * A class that can send a {@link Request}. + */ + interface RequestSender + { + /** + * Synchronously send a {@link Request} to the peer endpoint through this Channel + * over the underlying Connection and return the result of processing the + * Request. + * + * @param request the {@link Request} to send + * @param the expected response type + * + * @return the result sent by the peer + * + * @throws RuntimeException if an error or exception occurs while + * processing the Request. + * @throws RuntimeException if the Request is cancelled, a timeout occurs, + * or the waiting thread is interrupted + */ + R request(Request request); + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/NamedTopicRequest.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/NamedTopicRequest.java new file mode 100644 index 0000000000000..6aa8d4332d563 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/NamedTopicRequest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend.message.request; + +import com.tangosol.coherence.Component; + +import com.tangosol.coherence.component.net.extend.message.Request; + +import com.tangosol.net.messaging.Response; + +import com.tangosol.net.topic.NamedTopic; + +import com.tangosol.util.ListMap; + +import java.util.Map; + +/** + * A base class for topic requests. + * + * @author Jonathan Knight 2024.11.26 + */ +public class NamedTopicRequest + extends Request + implements TopicRequest + { + /** + * The target of this NamedTopicRequest. This property must be set by the + * Receiver before the run() method is called. + */ + private transient NamedTopic __m_NamedTopic; + + /** + * The approximate maximum number of bytes transferred by + * a partial response. + */ + private transient long __m_TransferThreshold; + + private static ListMap> __mapChildren; + + // Static initializer + static + { + __initStatic(); + } + + // Default static initializer + private static void __initStatic() + { + // register child classes + __mapChildren = new ListMap<>(); + __mapChildren.put("Status", Status.class); + } + + public NamedTopicRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + protected Map> get_ChildClasses() + { + return __mapChildren; + } + + // overridden to make these methods public so they can be accessed from the gRPC code. + @Override + public void setResponse(Response response) + { + super.setResponse(response); + } + + // overridden to make these methods public so they can be accessed from the gRPC code. + @Override + public Response getResponse() + { + return super.getResponse(); + } + + public void setNamedTopic(NamedTopic topic) + { + __m_NamedTopic = topic; + } + + public NamedTopic getNamedTopic() + { + return __m_NamedTopic; + } + + public long getTransferThreshold() + { + return __m_TransferThreshold; + } + + public void setTransferThreshold(long threshold) + { + __m_TransferThreshold = threshold; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicPublisherRequest.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicPublisherRequest.java new file mode 100644 index 0000000000000..57201f370bbc9 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicPublisherRequest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend.message.request; + +import com.tangosol.coherence.Component; +import com.tangosol.coherence.component.net.extend.proxy.TopicPublisherProxy; + +/** + * A base class for topic publisher requests. + * + * @author Jonathan Knight 2024.11.26 + */ +public class TopicPublisherRequest + extends NamedTopicRequest + { + /** + * The target publisher for this request. + */ + protected TopicPublisherProxy m_publisher; + + // ----- constructors --------------------------------------------------- + + public TopicPublisherRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the target {@link TopicPublisherProxy} for this request. + * + * @return the target {@link TopicPublisherProxy} for this request. + */ + public TopicPublisherProxy getPublisherConnector() + { + return m_publisher; + } + + /** + * Set the target {@link TopicPublisherProxy} for this request. + * + * @param connector the target {@link TopicPublisherProxy} for this request + */ + public void setPublisherConnector(TopicPublisherProxy connector) + { + m_publisher = connector; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicRequest.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicRequest.java new file mode 100644 index 0000000000000..b0cd2fa93ee7f --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicRequest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend.message.request; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Message; +import com.tangosol.net.messaging.Response; + +/** + * A common interface for topic service requests. + * + * @author Jonathan Knight 2024.11.26 + */ +public interface TopicRequest + extends Message + { + /** + * Set the {@link Channel} for the request. + * + * @param channel the {@link Channel} for the request + */ + void setChannel(Channel channel); + + /** + * Set the {@link Response} + * + * @param response the {@link Response} to use + */ + void setResponse(Response response); + + /** + * Return the {@link Response} + * + * @return the {@link Response} to used + */ + Response getResponse(); + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicServiceRequest.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicServiceRequest.java new file mode 100644 index 0000000000000..c1bff618ef29b --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicServiceRequest.java @@ -0,0 +1,194 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.message.request.TopicServiceRequest + +package com.tangosol.coherence.component.net.extend.message.request; + +import com.tangosol.coherence.component.net.extend.proxy.serviceProxy.TopicServiceProxy; +import com.tangosol.io.pof.PofReader; +import com.tangosol.net.TopicService; +import com.tangosol.net.messaging.Response; +import com.tangosol.util.ListMap; + +import java.util.Map; + +/** + * A base class for topic service requests. + * + * @author Jonathan Knight 2024.11.26 + */ +public abstract class TopicServiceRequest + extends com.tangosol.coherence.component.net.extend.message.Request + implements TopicRequest + { + // ---- Fields declarations ---- + + /** + * Property TopicName + */ + private String __m_TopicName; + + /** + * Property TopicService + */ + private transient TopicService __m_TopicService; + + /** + * Property TopicService proxy + */ + private transient TopicServiceProxy __m_TopicServiceProxy; + + /** + * Property TransferThreshold + */ + private transient long __m_TransferThreshold; + + private static ListMap> __mapChildren; + + static + { + __initStatic(); + } + + private static void __initStatic() + { + // register child classes + __mapChildren = new com.tangosol.util.ListMap(); + __mapChildren.put("Status", Status.get_CLASS()); + } + + public TopicServiceRequest(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + /** + * This is an auto-generated method that returns the map of design time + * [static] children. + *

+ * Note: the class generator will ignore any custom implementation for this + * behavior. + */ + @Override + protected Map> get_ChildClasses() + { + return __mapChildren; + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + return super.getDescription() + ", TopicName=" + getTopicName(); + } + + /** + * Getter for property TopicName.

+ */ + public String getTopicName() + { + return __m_TopicName; + } + + /** + * Setter for property TopicName.

+ */ + public void setTopicName(String sName) + { + __m_TopicName = sName; + } + + /** + * Getter for property TopicService.

+ */ + public TopicService getTopicService() + { + return __m_TopicService; + } + + /** + * Setter for property TopicService.

+ */ + public void setTopicService(TopicService service) + { + __m_TopicService = service; + } + + /** + * Getter for property TopicService proxy.

+ */ + public TopicServiceProxy getTopicServiceProxy() + { + return __m_TopicServiceProxy; + } + + /** + * Setter for property TopicService proxy.

+ */ + public void setTopicServiceProxy(TopicServiceProxy proxy) + { + __m_TopicServiceProxy = proxy; + } + + /** + * Getter for property TransferThreshold.

+ */ + public long getTransferThreshold() + { + return __m_TransferThreshold; + } + + /** + * Setter for property TransferThreshold.

+ */ + public void setTransferThreshold(long lThreshold) + { + __m_TransferThreshold = lThreshold; + } + + // overridden to make these methods public so they can be accessed from the gRPC code. + @Override + public void setResponse(Response response) + { + super.setResponse(response); + } + + // overridden to make these methods public so they can be accessed from the gRPC code. + @Override + public Response getResponse() + { + return super.getResponse(); + } + + @Override + public void readExternal(PofReader in) + throws java.io.IOException + { + super.readExternal(in); + __m_TopicName = in.readString(1); + } + + @Override + public void writeExternal(com.tangosol.io.pof.PofWriter out) + throws java.io.IOException + { + super.writeExternal(out); + out.writeString(1, __m_TopicName); + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicSubscriberRequest.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicSubscriberRequest.java new file mode 100644 index 0000000000000..92130804589bd --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/message/request/TopicSubscriberRequest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend.message.request; + +import com.tangosol.coherence.Component; +import com.tangosol.internal.net.topic.NamedTopicSubscriber; +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.util.Binary; + +/** + * A base class for topic subscriber requests. + * + * @author Jonathan Knight 2024.11.26 + */ +public class TopicSubscriberRequest + extends NamedTopicRequest + { + /** + * The target subscriber for this request. + */ + protected SubscriberConnector.ConnectedSubscriber m_subscriber; + + // ----- constructors --------------------------------------------------- + + public TopicSubscriberRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the target {@link SubscriberConnector.ConnectedSubscriber} for this request. + * + * @return the target {@link SubscriberConnector.ConnectedSubscriber} for this request. + */ + public SubscriberConnector.ConnectedSubscriber getSubscriber() + { + return m_subscriber; + } + + /** + * Set the target {@link SubscriberConnector.ConnectedSubscriber} for this request. + * + * @param connector the target {@link SubscriberConnector.ConnectedSubscriber} for this request + */ + public void setSubscriber(SubscriberConnector.ConnectedSubscriber connector) + { + m_subscriber = connector; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/BaseTopicMessageFactory.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/BaseTopicMessageFactory.java new file mode 100644 index 0000000000000..765843c8d68c0 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/BaseTopicMessageFactory.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.net.extend.messageFactory; + +import com.tangosol.coherence.Component; +import com.tangosol.coherence.component.net.extend.MessageFactory; +import com.tangosol.coherence.component.net.extend.message.Response; +import com.tangosol.util.ListMap; + +import java.util.Map; + +/** + * A base class for topics message factories. + * + * @author Jonathan Knight 2024.11.26 + */ +public class BaseTopicMessageFactory + extends MessageFactory + { + /** + * The message type identifier for a {@link TopicsResponse}. + */ + public static final int TYPE_ID_RESPONSE = 0; + + private static ListMap> __mapChildren; + + static + { + __initStatic(); + } + + private static void __initStatic() + { + // register child classes + __mapChildren = new ListMap<>(); + __mapChildren.put("Response", TopicsResponse.class); + } + + public BaseTopicMessageFactory() + { + this(null, null, true); + } + + public BaseTopicMessageFactory(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // containment initialization: children + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + @Override + protected Map> get_ChildClasses() + { + return __mapChildren; + } + + // ----- inner class: TopicsResponse ------------------------------------ + + /** + * A common {@link Response} for topics factories. + */ + public static class TopicsResponse + extends Response + { + /** + * Default constructor. + */ + public TopicsResponse() + { + this(null, null, true); + } + + /** + * Initializing constructor. + */ + public TopicsResponse(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + @Override + public int getTypeId() + { + return TYPE_ID_RESPONSE; + } + + @Override + public void run() + { + // no-op + } + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/NamedTopicFactory.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/NamedTopicFactory.java new file mode 100644 index 0000000000000..42de3ebf88c0a --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/NamedTopicFactory.java @@ -0,0 +1,1831 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory + +package com.tangosol.coherence.component.net.extend.messageFactory; + +import com.tangosol.coherence.Component; + +import com.tangosol.coherence.component.net.extend.Message; +import com.tangosol.coherence.component.net.extend.MessageFactory; +import com.tangosol.coherence.component.net.extend.RemotePublisher; +import com.tangosol.coherence.component.net.extend.RemoteSubscriber; +import com.tangosol.coherence.component.net.extend.RemoteSubscriberChannel; + +import com.tangosol.coherence.component.net.extend.message.Response; +import com.tangosol.coherence.component.net.extend.message.request.NamedTopicRequest; +import com.tangosol.coherence.component.net.extend.message.request.TopicPublisherRequest; +import com.tangosol.coherence.component.net.extend.message.request.TopicSubscriberRequest; + +import com.tangosol.coherence.component.net.extend.proxy.TopicPublisherProxy; + +import com.tangosol.internal.net.topic.NamedTopicPublisher; +import com.tangosol.internal.net.topic.NamedTopicSubscriber; +import com.tangosol.internal.net.topic.PublishResult; +import com.tangosol.internal.net.topic.PublisherChannelConnector; +import com.tangosol.internal.net.topic.ReceiveResult; +import com.tangosol.internal.net.topic.SeekResult; +import com.tangosol.internal.net.topic.SimpleReceiveResult; +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.internal.net.topic.SubscriberConnector.ConnectedSubscriber; +import com.tangosol.internal.net.topic.SubscriberConnector.SubscriberEvent; +import com.tangosol.internal.net.topic.TopicSubscription; + +import com.tangosol.internal.net.topic.impl.paged.agent.PollProcessor; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; + +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberId; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; + +import com.tangosol.net.messaging.Channel; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Position; +import com.tangosol.net.topic.Subscriber; + +import com.tangosol.util.Binary; +import com.tangosol.util.Filter; +import com.tangosol.util.ListMap; +import com.tangosol.util.UUID; +import com.tangosol.util.ValueExtractor; + +import java.io.IOException; + +import java.time.Instant; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.CompletableFuture; + +/** + * The {@link MessageFactory} to create messages for the {@link NamedTopic} + * Extend protocol. + * + * @author Jonathan Knight 2024.11.26 + */ +public class NamedTopicFactory + extends BaseTopicMessageFactory + { + private static ListMap> __mapChildren; + + public static final int TYPE_ID_REMAINING_MESSAGES = 1; + public static final int TYPE_ID_ENSURE_SUBSCRIBER_GROUP = 2; + public static final int TYPE_ID_DESTROY_SUBSCRIBER_GROUP = 3; + public static final int TYPE_ID_DESTROY_EVENT = 4; + public static final int TYPE_ID_PUBLISHER_EVENT = 6; + public static final int TYPE_ID_PUBLISH = 7; + public static final int TYPE_ID_INITIALIZE_SUBSCRIPTION = 10; + public static final int TYPE_ID_ENSURE_SUBSCRIPTION = 11; + public static final int TYPE_ID_GET_SUBSCRIPTION = 12; + public static final int TYPE_ID_GET_OWNED_CHANNELS = 13; + public static final int TYPE_ID_RECEIVE = 14; + public static final int TYPE_ID_PEEK = 16; + public static final int TYPE_ID_COMMIT = 17; + public static final int TYPE_ID_IS_COMMITTED = 19; + public static final int TYPE_ID_GET_LAST_COMMITTED = 20; + public static final int TYPE_ID_GET_HEADS = 21; + public static final int TYPE_ID_GET_TAILS = 22; + public static final int TYPE_ID_SEEK = 23; + public static final int TYPE_ID_HEARTBEAT = 24; + public static final int TYPE_ID_SUBSCRIBER_EVENT = 25; + + static + { + __initStatic(); + } + + private static void __initStatic() + { + // register child classes + __mapChildren = new ListMap<>(); + __mapChildren.put("Response", TopicsResponse.class); + __mapChildren.put("GetRemainingMessagesRequest", GetRemainingMessagesRequest.class); + __mapChildren.put("EnsureSubscriberGroupRequest", EnsureSubscriberGroupRequest.class); + __mapChildren.put("DestroySubscriberGroupRequest", DestroySubscriberGroupRequest.class); + __mapChildren.put("PublisherEvent", PublisherEvent.class); + __mapChildren.put("PublishRequest", PublishRequest.class); + __mapChildren.put("InitializeSubscriptionRequest", InitializeSubscriptionRequest.class); + __mapChildren.put("EnsureSubscriptionRequest", EnsureSubscriptionRequest.class); + __mapChildren.put("GetSubscriptionRequest", GetSubscriptionRequest.class); + __mapChildren.put("GetOwnedChannelsRequest", GetOwnedChannelsRequest.class); + __mapChildren.put("ReceiveRequest", ReceiveRequest.class); + __mapChildren.put("PeekRequest", PeekRequest.class); + __mapChildren.put("CommitRequest", CommitRequest.class); + __mapChildren.put("IsCommitedRequest", IsCommitedRequest.class); + __mapChildren.put("GetLastCommitedRequest", GetLastCommitedRequest.class); + __mapChildren.put("GetHeadsRequest", GetHeadsRequest.class); + __mapChildren.put("GetTailsRequest", GetTailsRequest.class); + __mapChildren.put("SeekRequest", SeekRequest.class); + __mapChildren.put("HeartbeatRequest", HeartbeatRequest.class); + __mapChildren.put("SubscriberEvent", SubscriberChannelEvent.class); + __mapChildren.put("DestroyEvent", DestroyEvent.class); + } + + public NamedTopicFactory() + { + super(null, null, true); + } + + @Override + protected Map> get_ChildClasses() + { + return __mapChildren; + } + + // ----- inner class: GetRemainingMessagesRequest ----------------------- + + /** + * A message to return the number of messages remaining in a topic + * for a subscriber group.. + */ + public static class GetRemainingMessagesRequest + extends NamedTopicRequest + { + private String m_sSubscriberGroup; + + private int[] m_anChannel; + + public GetRemainingMessagesRequest() + { + super(null, null, true); + } + + public GetRemainingMessagesRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_REMAINING_MESSAGES; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + NamedTopic topic = getNamedTopic(); + _assert(topic != null); + _assert(m_sSubscriberGroup != null); + int count; + if (m_anChannel == null) + { + //noinspection DataFlowIssue + count = topic.getRemainingMessages(m_sSubscriberGroup); + } + else + { + //noinspection DataFlowIssue + count = topic.getRemainingMessages(m_sSubscriberGroup, m_anChannel); + } + response.setResult(count); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_sSubscriberGroup = in.readString(10); + m_anChannel = in.readIntArray(11); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeString(10, m_sSubscriberGroup); + out.writeIntArray(11, m_anChannel); + } + + public void setSubscriberGroup(String sSubscriberGroup) + { + m_sSubscriberGroup = sSubscriberGroup; + } + + public void setChannels(int[] anChannel) + { + m_anChannel = anChannel; + } + } + + // ----- inner class: GetRemainingMessagesRequest ----------------------- + + /** + * A message to ensure a subscriber group. + */ + public static class EnsureSubscriberGroupRequest + extends NamedTopicRequest + { + private String m_sSubscriberGroup; + + private Filter m_filter; + + private ValueExtractor m_extractor; + + public EnsureSubscriberGroupRequest() + { + super(null, null, true); + } + + public EnsureSubscriberGroupRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_ENSURE_SUBSCRIBER_GROUP; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + NamedTopic topic = getNamedTopic(); + _assert(topic != null); + //noinspection DataFlowIssue + topic.ensureSubscriberGroup(m_sSubscriberGroup, m_filter, m_extractor); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_sSubscriberGroup = in.readString(10); + m_filter = in.readObject(11); + m_extractor = in.readObject(12); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeString(10, m_sSubscriberGroup); + out.writeObject(11, m_filter); + out.writeObject(12, m_extractor); + } + + public void setSubscriberGroup(String sSubscriberGroup) + { + m_sSubscriberGroup = sSubscriberGroup; + } + + public void setFilter(Filter filter) + { + m_filter = filter; + } + + public void setExtractor(ValueExtractor extractor) + { + m_extractor = extractor; + } + } + + // ----- inner class: GetRemainingMessagesRequest ----------------------- + + /** + * A message to destroy a subscriber group. + */ + public static class DestroySubscriberGroupRequest + extends NamedTopicRequest + { + private String m_sSubscriberGroup; + + public DestroySubscriberGroupRequest() + { + super(null, null, true); + } + + public DestroySubscriberGroupRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_DESTROY_SUBSCRIBER_GROUP; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + NamedTopic topic = getNamedTopic(); + _assert(topic != null); + _assert(m_sSubscriberGroup != null); + //noinspection DataFlowIssue + topic.destroySubscriberGroup(m_sSubscriberGroup); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_sSubscriberGroup = in.readString(10); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeString(10, m_sSubscriberGroup); + } + + public void setSubscriberGroup(String sSubscriberGroup) + { + m_sSubscriberGroup = sSubscriberGroup; + } + } + + // ----- inner class: SubscriberIdOptionRequest ------------------------- + + /** + * A {@link Subscriber.Option} to set the subscriber identifier. + */ + @SuppressWarnings("rawtypes") + protected static class SubscriberIdOption + implements NamedTopicSubscriber.WithSubscriberId + { + public SubscriberIdOption(int nId, UUID uuid) + { + m_nId = nId; + m_uuid = uuid; + } + + @Override + public SubscriberId getId(int nNotificationId) + { + return new SubscriberId(nNotificationId, m_nId, m_uuid); + } + + // ----- data members ----------------------------------------------- + + private final int m_nId; + + private final UUID m_uuid; + } + + // ----- inner class: PublishRequest ------------------------------------ + + /** + * A message to publish values to a topic. + */ + public static class PublishRequest + extends TopicPublisherRequest + { + /** + * The channel to publish to. + */ + protected int m_nChannel; + + /** + * The list of serialized binary values to publish. + */ + protected List m_listBinary; + + /** + * The notification identifier. + */ + protected int m_nNotify; + + public PublishRequest() + { + super(null, null, true); + } + + public PublishRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_PUBLISH; + } + + @Override + @SuppressWarnings("DataFlowIssue") + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + TopicPublisherProxy proxy = getPublisherConnector(); + _assert(proxy != null); + + PublisherChannelConnector connector = proxy.getChannelConnector(m_nChannel); + _assert(connector != null); + + CompletableFuture future = new CompletableFuture<>(); + connector.offer(null, m_listBinary, m_nNotify, (result, errOffer) -> + { + if (errOffer != null) + { + future.completeExceptionally(errOffer); + } + else + { + future.complete(result); + } + }); + PublishResult result = future.join(); + response.setResult(result); + } + + public void setChannel(int nChannel) + { + m_nChannel = nChannel; + } + + public void setBinaries(List list) + { + m_listBinary = list; + } + + public void setNotify(int nNotify) + { + m_nNotify = nNotify; + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nChannel = in.readInt(10); + m_listBinary = in.readCollection(11, new ArrayList<>()); + m_nNotify = in.readInt(12); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeInt(10, m_nChannel); + out.writeCollection(11, m_listBinary, Binary.class); + out.writeInt(12, m_nNotify); + } + } + + // ----- inner class: PublisherEventRequest ----------------------------- + + /** + * A message to dispatch a {@link NamedTopicPublisher.PublisherEvent}. + */ + public static class PublisherEvent + extends Message + { + /** + * The event type. + */ + private NamedTopicPublisher.PublisherEvent.Type m_nType; + + /** + * The event channels. + */ + private int[] m_anChannel; + + // ----- constructors ----------------------------------------------- + + public PublisherEvent() + { + this(null, null, true); + } + + public PublisherEvent(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + // ----- accessors ---------------------------------------------------- + + /** + * Return the message type. + * @return the message type + */ + public NamedTopicPublisher.PublisherEvent.Type getType() + { + return m_nType; + } + + /** + * Set the event type. + * + * @param nType the event type + */ + public void setType(NamedTopicPublisher.PublisherEvent.Type nType) + { + m_nType = nType; + } + + /** + * Obtain the channels this event applies to. + * + * @return the channels this event applies to + */ + public int[] getChannels() + { + return m_anChannel; + } + + /** + * Set the event channels + * + * @param anChannel the event channels + */ + public void setChannels(int[] anChannel) + { + m_anChannel = anChannel; + } + + // ----- Message methods -------------------------------------------- + + @Override + public int getTypeId() + { + return TYPE_ID_PUBLISHER_EVENT; + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + if (m_anChannel == null) + { + return super.getDescription() + + ", Type=" + m_nType + + ", Channels="; + } + return super.getDescription() + + ", Type=" + m_nType + + ", Channels=" + Arrays.toString(m_anChannel); + } + + @Override + public void run() + { + Channel channel = getChannel(); + RemotePublisher publisher = (RemotePublisher) channel.getReceiver(); + NamedTopicPublisher.PublisherEvent.Type type = getType(); + int[] anChannel = getChannels(); + + if (anChannel == null) + { + publisher.dispatchEvent(type); + } + else + { + publisher.dispatchEvent(type, anChannel); + } + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nType = in.readObject(10); + m_anChannel = in.readIntArray(20); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeObject(10, m_nType); + out.writeIntArray(20, m_anChannel); + } + } + + // ----- inner class: InitializeSubscriptionRequest --------------------- + + /** + * A message to initialize a subscription for a subscriber. + */ + public static class InitializeSubscriptionRequest + extends TopicSubscriberRequest + { + private boolean m_fForceReconnect; + + private boolean m_fReconnect; + + private boolean m_fDisconnected; + + public InitializeSubscriptionRequest() + { + this(null, null, true); + } + + public InitializeSubscriptionRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setForceReconnect(boolean fForceReconnect) + { + m_fForceReconnect = fForceReconnect; + } + + public void setReconnect(boolean fReconnect) + { + m_fReconnect = fReconnect; + } + + public void setDisconnected(boolean fDisconnected) + { + m_fDisconnected = fDisconnected; + } + + @Override + public int getTypeId() + { + return TYPE_ID_INITIALIZE_SUBSCRIPTION; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + + Position[] aHead = subscriber.getConnector() + .initialize(subscriber, m_fForceReconnect, m_fReconnect, m_fDisconnected); + + long lSubscriptionId = subscriber.getSubscriptionId(); + long nTimestamp = subscriber.getConnectionTimestamp(); + response.setResult(new Object[]{lSubscriptionId, nTimestamp, aHead}); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_fForceReconnect = in.readBoolean(10); + m_fReconnect = in.readBoolean(11); + m_fDisconnected = in.readBoolean(12); + } + + @Override + public void writeExternal(PofWriter out) throws IOException + { + super.writeExternal(out); + out.writeBoolean(10, m_fForceReconnect); + out.writeBoolean(11, m_fReconnect); + out.writeBoolean(12, m_fDisconnected); + } + } + + // ----- inner class: EnsureSubscriptionRequest ------------------------- + + /** + * A message to ensure a subscription for a subscriber. + */ + public static class EnsureSubscriptionRequest + extends TopicSubscriberRequest + { + private boolean m_fForceReconnect; + + private long m_subscriptionId; + + public EnsureSubscriptionRequest() + { + super(null, null, true); + } + + public EnsureSubscriptionRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setForceReconnect(boolean fForceReconnect) + { + m_fForceReconnect = fForceReconnect; + } + + public void setSubscriptionId(long subscriptionId) + { + m_subscriptionId = subscriptionId; + } + + @Override + public int getTypeId() + { + return TYPE_ID_ENSURE_SUBSCRIPTION; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + + boolean f = subscriber.getConnector() + .ensureSubscription(subscriber, m_subscriptionId, m_fForceReconnect); + response.setResult(f); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_fForceReconnect = in.readBoolean(10); + m_subscriptionId = in.readLong(11); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeBoolean(10, m_fForceReconnect); + out.writeLong(11, m_subscriptionId); + } + } + + // ----- inner class: GetSubscriptionRequest ---------------------------- + + /** + * A message to obtain a subscription. + */ + public static class GetSubscriptionRequest + extends TopicSubscriberRequest + { + private long m_lSubscriptionId; + + public GetSubscriptionRequest() + { + super(null, null, true); + } + + public GetSubscriptionRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setSubscriptionId(long id) + { + m_lSubscriptionId = id; + } + + @Override + public int getTypeId() + { + return TYPE_ID_GET_SUBSCRIPTION; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + TopicSubscription subscription = subscriber.getConnector().getSubscription(subscriber, m_lSubscriptionId); + response.setResult(subscription); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_lSubscriptionId = in.readLong(10); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeLong(10, m_lSubscriptionId); + } + } + + // ----- inner class: GetOwnedChannelsRequest -------------------------- + + /** + * A subscriber request to obtain the channels owned by a subscriber. + */ + public static class GetOwnedChannelsRequest + extends TopicSubscriberRequest + { + public GetOwnedChannelsRequest() + { + super(null, null, true); + } + + public GetOwnedChannelsRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_GET_OWNED_CHANNELS; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + SortedSet channels = subscriber.getConnector().getOwnedChannels(subscriber); + response.setResult(channels); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + } + } + + // ----- inner class: ReceiveRequest ------------------------------------ + + /** + * A subscriber request to receive messages from a topic. + */ + public static class ReceiveRequest + extends TopicSubscriberRequest + { + private int m_nChannel; + + private Position m_position; + + private long m_lVersion; + + public ReceiveRequest() + { + super(null, null, true); + } + + public ReceiveRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setPosition(Position position) + { + m_position = position; + } + + public void setVersion(long lVersion) + { + m_lVersion = lVersion; + } + + public void setChannel(int nChannel) + { + m_nChannel = nChannel; + } + + @Override + public int getTypeId() + { + return TYPE_ID_RECEIVE; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + CompletableFuture future = new CompletableFuture<>(); + subscriber.receive(m_nChannel, new Handler(future, response)).join(); + } + + protected class Handler + implements SubscriberConnector.ReceiveHandler + { + public Handler(CompletableFuture future, com.tangosol.coherence.component.net.extend.message.Response response) + { + f_future = future; + f_response = response; + } + + @Override + public void onReceive(long lVersion, ReceiveResult result, Throwable error, SubscriberConnector.Continuation continuation) + { + try + { + Channel channel = getChannel(); + _assert(channel != null); + + ConnectedSubscriber subscriber = getSubscriber(); + Position head = subscriber.getChannelHead(m_nChannel); + + if (result instanceof PollProcessor.Result) + { + result = ((PollProcessor.Result) result).toSimpleResult(head); + } + else + { + result = new SimpleReceiveResult(result.getElements(), + result.getRemainingElementCount(), result.getStatus(), head); + } + + continuation.onContinue(); + + if (error == null) + { + f_response.setResult(result); + } + else + { + f_response.setFailure(true); + f_response.setResult(error); + } + } + catch (Throwable t) + { + f_response.setFailure(true); + f_response.setResult(t); + } + f_future.complete(null); + } + + // ----- data members ------------------------------------------- + + private final CompletableFuture f_future; + + com.tangosol.coherence.component.net.extend.message.Response f_response; + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nChannel = in.readInt(10); + m_position = in.readObject(11); + m_lVersion = in.readLong(12); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeInt(10, m_nChannel); + out.writeObject(11, m_position); + out.writeLong(12, m_lVersion); + } + } + + // ----- inner class: PeekRequest --------------------------------------- + + /** + * A subscriber request to peek at a message in a topic. + */ + public static class PeekRequest + extends TopicSubscriberRequest + { + private int m_nChannel; + + private Position m_position; + + public PeekRequest() + { + super(null, null, true); + } + + public PeekRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setChannel(int nChannel) + { + m_nChannel = nChannel; + } + + public void setPosition(Position position) + { + m_position = position; + } + + @Override + public int getTypeId() + { + return TYPE_ID_PEEK; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + Subscriber.Element element = subscriber.getConnector().peek(m_nChannel, m_position); + response.setResult(element); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nChannel = in.readInt(10); + m_position = in.readObject(11); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeObject(10, m_nChannel); + out.writeObject(11, m_position); + } + } + + // ----- inner class: CommitRequest ------------------------------------- + + /** + * A subscriber request to commit a position in a channel. + */ + public static class CommitRequest + extends TopicSubscriberRequest + { + /** + * The identifier in the response map of the {@link Subscriber.CommitResult}. + */ + public static final int RESPONSE_ID_RESULT = 0; + /** + * The identifier in the response map of the head position. + */ + public static final int RESPONSE_ID_HEAD = 1; + + /** + * The channel to commit. + */ + private int m_nChannel; + + /** + * The position in the channel to commit. + */ + private Position m_position; + + /** + * Default constructor. + */ + public CommitRequest() + { + this(null, null, true); + } + + /** + * TDE Component constructor. + */ + public CommitRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + /** + * Set the channel to commit. + * + * @param nChannel the channel to commit + */ + public void setChannel(int nChannel) + { + m_nChannel = nChannel; + } + + /** + * Set the position in the channel to commit. + * + * @param position the position in the channel to commit + */ + public void setPosition(Position position) + { + m_position = position; + } + + @Override + public int getTypeId() + { + return TYPE_ID_COMMIT; + } + + @Override + protected void onRun(Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + + Subscriber.CommitResult result = subscriber.commitAsync(m_nChannel, m_position).join(); + Position head = subscriber.getChannelHead(m_nChannel); + Object[] oResult = new Object[2]; + + oResult[RESPONSE_ID_RESULT] = result; + oResult[RESPONSE_ID_HEAD] = head; + response.setResult(oResult); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nChannel = in.readInt(10); + m_position = in.readObject(11); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeObject(10, m_nChannel); + out.writeObject(11, m_position); + } + } + + // ----- inner class: IsCommitedRequest --------------------------------- + + /** + * A subscriber request to determine whether a position in a channel + * has been committed. + */ + public static class IsCommitedRequest + extends TopicSubscriberRequest + { + private int m_nChannel; + + private Position m_position; + + public IsCommitedRequest() + { + this(null, null, true); + } + + public IsCommitedRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setChannel(int nChannel) + { + m_nChannel = nChannel; + } + + public void setPosition(Position position) + { + m_position = position; + } + + @Override + public int getTypeId() + { + return TYPE_ID_IS_COMMITTED; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + SubscriberGroupId groupId = subscriber.getSubscriberGroupId(); + boolean fCommitted = subscriber.getConnector().isCommitted(groupId, m_nChannel, m_position); + response.setResult(fCommitted); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nChannel = in.readInt(10); + m_position = in.readObject(11); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeObject(10, m_nChannel); + out.writeObject(11, m_position); + } + } + + // ----- inner class: GetLastCommitedRequest ---------------------------- + + /** + * A subscriber request to obtain the last committed position. + */ + public static class GetLastCommitedRequest + extends TopicSubscriberRequest + { + public GetLastCommitedRequest() + { + this(null, null, true); + } + + public GetLastCommitedRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_GET_LAST_COMMITTED; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + SubscriberGroupId groupId = subscriber.getSubscriberGroupId(); + Map map = subscriber.getConnector().getLastCommittedInGroup(groupId); + response.setResult(map); + } + } + + // ----- inner class: GetHeadsRequest ----------------------------------- + + /** + * A subscriber request to obtain the current head positions. + */ + public static class GetHeadsRequest + extends TopicSubscriberRequest + { + private int[] m_anChannel; + + public GetHeadsRequest() + { + this(null, null, true); + } + + public GetHeadsRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setChannels(int[] anChannel) + { + m_anChannel = anChannel; + } + + @Override + public int getTypeId() + { + return TYPE_ID_GET_HEADS; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + Map map = subscriber.getConnector().getTopicHeads(m_anChannel); + response.setResult(map); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_anChannel = in.readIntArray(10); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeIntArray(10, m_anChannel); + } + } + + // ----- inner class: GetTailsRequest ----------------------------------- + + /** + * A subscriber request to obtain the current head positions. + */ + public static class GetTailsRequest + extends TopicSubscriberRequest + { + public GetTailsRequest() + { + super(null, null, true); + } + + public GetTailsRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + public int getTypeId() + { + return TYPE_ID_GET_TAILS; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + Map map = subscriber.getConnector().getTopicTails(); + response.setResult(map); + } + } + + // ----- inner class: SeekRequest --------------------------------------- + + /** + * A subscriber request to move the head positions. + */ + public static class SeekRequest + extends TopicSubscriberRequest + { + private Map m_mapPosition; + + private Map m_mapTimestamp; + + public SeekRequest() + { + this(null, null, true); + } + + public SeekRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setPositions(Map mapPosition) + { + m_mapPosition = mapPosition; + } + + public void setTimestamps(Map mapTimestamp) + { + m_mapTimestamp = mapTimestamp; + } + + @Override + public int getTypeId() + { + return TYPE_ID_SEEK; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + SubscriberConnector connector = subscriber.getConnector(); + + Map mapResult; + if (m_mapPosition != null && !m_mapPosition.isEmpty()) + { + mapResult = connector.seekToPosition(subscriber, m_mapPosition); + } + else if (m_mapTimestamp != null && !m_mapTimestamp.isEmpty()) + { + mapResult = connector.seekToTimestamp(subscriber, m_mapTimestamp); + } + else + { + throw new IllegalArgumentException("Neither the seek position nor timestamp have been set"); + } + response.setResultAsEntrySet(mapResult.entrySet()); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_mapPosition = in.readMap(11, new HashMap<>()); + m_mapTimestamp = in.readMap(12, new HashMap<>()); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeMap(11, m_mapPosition); + out.writeMap(12, m_mapTimestamp, Integer.class, Instant.class); + } + } + + // ----- inner class: HeartbeatRequest ---------------------------------- + + /** + * A subscriber request to send a heartbeat. + */ + public static class HeartbeatRequest + extends TopicSubscriberRequest + { + private boolean m_fAsync; + + public HeartbeatRequest() + { + super(null, null, true); + } + + public HeartbeatRequest(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + public void setAsync(boolean f) + { + m_fAsync = f; + } + + @Override + public int getTypeId() + { + return TYPE_ID_HEARTBEAT; + } + + @Override + protected void onRun(com.tangosol.coherence.component.net.extend.message.Response response) + { + ConnectedSubscriber subscriber = getSubscriber(); + _assert(subscriber != null); + //noinspection DataFlowIssue + subscriber.getConnector().heartbeat(subscriber, m_fAsync); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_fAsync = in.readBoolean(10); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeBoolean(10, m_fAsync); + } + } + + // ----- inner class: SubscriberChannelEvent ---------------------------- + + /** + * A message representing a subscriber channel event. + */ + public static class SubscriberChannelEvent + extends Message + { + /** + * The event type. + */ + private int m_nType; + + /** + * The event channels. + */ + private int[] m_anChannel; + + /** + * The event channels. + */ + private SortedSet m_setChannel; + + // ----- constructors ----------------------------------------------- + + public SubscriberChannelEvent() + { + this(null, null, true); + } + + public SubscriberChannelEvent(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + // ----- accessors ---------------------------------------------------- + + /** + * Return the message type. + * @return the message type + */ + public int getType() + { + return m_nType; + } + + /** + * Set the event type. + * + * @param nType the event type + */ + public void setType(int nType) + { + m_nType = nType; + } + + /** + * Return the event type. + * + * @return the event type + */ + public SubscriberEvent.Type getEventType() + { + return SubscriberEvent.Type.values()[m_nType]; + } + + /** + * Obtain the channels this event applies to. + * + * @return the channels this event applies to + */ + public int[] getPopulatedChannels() + { + return m_anChannel; + } + + /** + * Set the event channels + * + * @param anChannel the event channels + */ + public void setPopulatedChannels(int[] anChannel) + { + m_anChannel = anChannel; + } + + /** + * Obtain the channels this event applies to. + * + * @return the channels this event applies to + */ + public SortedSet getAllocatedChannels() + { + return m_setChannel; + } + + /** + * Set the event channels + * + * @param set the event channels + */ + public void setAllocatedChannels(SortedSet set) + { + m_setChannel = set; + } + + // ----- Message methods -------------------------------------------- + + @Override + public int getTypeId() + { + return TYPE_ID_SUBSCRIBER_EVENT; + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + return super.getDescription() + + ", Type=" + m_nType + + ", PopulatedChannels=" + Arrays.toString(m_anChannel) + + ", AllocatedChannels=" + m_setChannel; + } + + @Override + public void run() + { + Channel channel = getChannel(); + RemoteSubscriberChannel subscriberChannel = (RemoteSubscriberChannel) channel.getReceiver(); + RemoteSubscriber subscriber = subscriberChannel.getSubscriber(); + SubscriberEvent event = createEvent(); + subscriber.dispatchEvent(event); + } + + /** + * Create a {@link SubscriberEvent}. + * + * @return a {@link SubscriberEvent} + */ + public SubscriberEvent createEvent() + { + Channel channel = getChannel(); + RemoteSubscriberChannel subscriberChannel = (RemoteSubscriberChannel) channel.getReceiver(); + RemoteSubscriber subscriber = subscriberChannel.getSubscriber(); + SubscriberEvent.Type type = SubscriberEvent.Type.values()[m_nType]; + return new SubscriberEvent(subscriber, type, m_anChannel, m_setChannel); + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_nType = in.readInt(10); + m_anChannel = in.readIntArray(11); + m_setChannel = in.readCollection(12, new TreeSet<>()); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeInt(10, m_nType); + out.writeIntArray(11, m_anChannel); + out.writeCollection(12, m_setChannel, Integer.class); + } + } + + // ----- inner class: DestroyEvent ------------------------------------------ + + /** + * An event message to signal destruction of a NamedTopic. + */ + public static class DestroyEvent + extends Message + { + /** + * Property ScopeName + *

+ * The scope name of the topic service. + */ + private String __m_ScopeName; + + /** + * Property TopicName + *

+ * The name of the topic that has been destroyed. + */ + private String __m_TopicName; + + public DestroyEvent() + { + this(null, null, true); + } + + public DestroyEvent(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + @Override + protected String getDescription() + { + return super.getDescription() + + " scope=" + getScopeName() + + " topic=" + getTopicName(); + } + + /** + * Getter for property ScopeName.

+ * The scope name of the topic service. + */ + public String getScopeName() + { + return __m_ScopeName; + } + + /** + * Getter for property TopicName.

+ * The name of the topic that has been destroyed. + */ + public String getTopicName() + { + return __m_TopicName; + } + + @Override + public int getTypeId() + { + return TYPE_ID_DESTROY_EVENT; + } + + @Override + public boolean isExecuteInOrder() + { + return true; + } + + @Override + public void run() + { + throw new UnsupportedOperationException("Implement this method!"); + } + + /** + * Setter for property ScopeName.

+ * The scope name of the topic service. + */ + public void setScopeName(String sName) + { + __m_ScopeName = sName; + } + + /** + * Setter for property TopicName.

+ * The name of the topic that has been destroyed. + */ + public void setTopicName(String sName) + { + __m_TopicName = sName; + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + setScopeName(in.readString(0)); + setTopicName(in.readString(1)); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + + out.writeString(0, getScopeName()); + out.writeString(1, getTopicName()); + } + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/TopicServiceFactory.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/TopicServiceFactory.java new file mode 100644 index 0000000000000..3c59c13780ad8 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/messageFactory/TopicServiceFactory.java @@ -0,0 +1,810 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.messageFactory.TopicServiceFactory + +package com.tangosol.coherence.component.net.extend.messageFactory; + +import com.tangosol.coherence.component.net.extend.MessageFactory; + +import com.tangosol.coherence.component.net.extend.message.Response; +import com.tangosol.coherence.component.net.extend.message.request.TopicServiceRequest; + +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; + +import com.tangosol.coherence.component.net.extend.proxy.NamedTopicProxy; +import com.tangosol.coherence.component.net.extend.proxy.TopicPublisherProxy; +import com.tangosol.coherence.component.net.extend.proxy.TopicSubscriberProxy; + +import com.tangosol.coherence.component.util.daemon.queueProcessor.service.Peer; +import com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.ProxyService; + +import com.tangosol.internal.net.topic.NamedTopicPublisher; +import com.tangosol.internal.net.topic.PublisherConnector; +import com.tangosol.internal.net.topic.SubscriberConnector.ConnectedSubscriber; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; + +import com.tangosol.net.Member; +import com.tangosol.net.Service; +import com.tangosol.net.TopicService; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Connection; +import com.tangosol.net.messaging.ConnectionManager; + +import com.tangosol.net.topic.NamedTopic; + +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.util.Binary; +import com.tangosol.util.Filter; +import com.tangosol.util.ListMap; +import com.tangosol.util.ValueExtractor; + +import java.io.IOException; + +import java.net.URI; + +import java.util.Map; + +/** + * The {@link MessageFactory} to create messages for the + * topic service Extend protocol. + * + * @author Jonathan Knight 2024.11.26 + */ +public class TopicServiceFactory + extends BaseTopicMessageFactory + { + public static final int TYPE_ID_CHANNEL_COUNT = 1; + public static final int TYPE_ID_DESTROY_TOPIC = 3; + public static final int TYPE_ID_ENSURE_CHANNEL_COUNT = 4; + public static final int TYPE_ID_ENSURE_TOPIC = 5; + public static final int TYPE_ID_GET_SUBSCRIBER_GROUPS = 6; + public static final int TYPE_ID_ENSURE_PUBLISHER = 8; + public static final int TYPE_ID_ENSURE_SUBSCRIBER = 9; + public static final int TYPE_ID_DESTROY_PUBLISHER = 10; + public static final int TYPE_ID_DESTROY_SUBSCRIBER = 11; + + private static ListMap> __mapChildren; + + static + { + __initStatic(); + } + + private static void __initStatic() + { + // register child classes + __mapChildren = new ListMap<>(); + __mapChildren.put("ChannelCountRequest", ChannelCountRequest.class); + __mapChildren.put("DestroyTopicRequest", DestroyTopicRequest.class); + __mapChildren.put("EnsureChannelCountRequest", EnsureChannelCountRequest.class); + __mapChildren.put("EnsureTopicRequest", EnsureTopicRequest.class); + __mapChildren.put("GetSubscriberGroupsRequest", GetSubscriberGroupsRequest.class); + __mapChildren.put("EnsurePublisherRequest", EnsurePublisherRequest.class); + __mapChildren.put("EnsureSubscriberRequest", EnsureSubscriberRequest.class); + __mapChildren.put("Response", TopicsResponse.class); + } + + public TopicServiceFactory() + { + super(null, null, true); + } + + @Override + protected Map> get_ChildClasses() + { + return __mapChildren; + } + + // ----- inner class: ChannelCountRequest ------------------------------- + + /** + * A request to obtain the channel count for a topic. + */ + public static class ChannelCountRequest + extends TopicServiceRequest + { + public ChannelCountRequest() + { + super(null, null, true); + } + + @Override + public int getTypeId() + { + return TYPE_ID_CHANNEL_COUNT; + } + + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + _assert(service != null); + //noinspection DataFlowIssue + int cChannel = service.getChannelCount(getTopicName()); + response.setResult(cChannel); + } + } + + // ----- inner class: DestroyTopicRequest ------------------------------- + + /** + * A request to destroy a topic. + */ + public static class DestroyTopicRequest + extends TopicServiceRequest + { + public DestroyTopicRequest() + { + super(null, null, true); + } + + @Override + public int getTypeId() + { + return TYPE_ID_DESTROY_TOPIC; + } + + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + NamedTopic topic = service.ensureTopic(getTopicName(), null); + //service.destroyTopic(topic); + topic.destroy(); + response.setResult(Boolean.TRUE); + } + } + + // ----- EnsureChannelCountRequest -------------------------------------- + + /** + * A request to ensure a topic has a minimum number of channels. + */ + public static class EnsureChannelCountRequest + extends TopicServiceRequest + { + /** + * Property ChannelCount + */ + private int __m_ChannelCount; + + /** + * Property RequiredChannels + */ + private int __m_RequiredChannels; + + public EnsureChannelCountRequest() + { + super(null, null, true); + } + + @Override + public int getTypeId() + { + return TYPE_ID_ENSURE_CHANNEL_COUNT; + } + + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + int cChannel = service.ensureChannelCount(getTopicName(), __m_RequiredChannels, __m_ChannelCount); + response.setResult(cChannel); + } + + /** + * Setter for property ChannelCount.

+ */ + public void setChannelCount(int nCount) + { + __m_ChannelCount = nCount; + } + + /** + * Setter for property RequiredChannels.

+ */ + public void setRequiredChannels(int nChannels) + { + __m_RequiredChannels = nChannels; + } + + /** + * Getter for property ChannelCount.

+ */ + public int getChannelCount() + { + return __m_ChannelCount; + } + + /** + * Getter for property RequiredChannels.

+ */ + public int getRequiredChannels() + { + return __m_RequiredChannels; + } + + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + + setRequiredChannels(in.readInt(2)); + setChannelCount(in.readInt(3)); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + + out.writeInt(2, getRequiredChannels()); + out.writeInt(3, getChannelCount()); + } + } + + // ----- EnsureTopicRequest --------------------------------------------- + + /** + * A request that creates a new channel. + */ + public static abstract class NewChannelRequest + extends TopicServiceRequest + { + protected NewChannelRequest() + { + super(null, null, true); + } + + protected URI createChannel(Channel.Receiver receiver) + { + Channel channel = getChannel(); + Connection connection = channel.getConnection(); + URI uri = connection.createChannel(NamedTopicProtocol.getInstance(), null, receiver); + if (m_fAutoAccept) + { + connection.acceptChannel(uri, null, receiver, null); + } + return uri; + } + + // ----- data members ----------------------------------------------- + + /** + * A flag that is {@code true} to indicate the new channel should be immediately accepted. + * This is not normal Extend behaviour and is used by gRPC to accept channels. + */ + protected boolean m_fAutoAccept; + } + + // ----- EnsureTopicRequest --------------------------------------------- + + /** + * The ensure topic request. + */ + public static class EnsureTopicRequest + extends NewChannelRequest + { + public EnsureTopicRequest() + { + } + + @Override + public int getTypeId() + { + return TYPE_ID_ENSURE_TOPIC; + } + + /** + * Called when the Request is run. + * + * @param response the Response that should be populated with the + * result of running the Request + */ + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + String sName = getTopicName(); + + if (sName == null || sName.isEmpty()) + { + throw new IllegalArgumentException("The topic name cannot be null or blank"); + } + + NamedTopic topic = service.ensureTopic(sName, null); + NamedTopicProxy proxy = new NamedTopicProxy(); + proxy.setNamedTopic(topic); + proxy.setTransferThreshold(getTransferThreshold()); + + URI uri = createChannel(proxy); + + ConnectionManager manager = getChannel().getConnection().getConnectionManager(); + if (manager instanceof Peer) + { + Service parentService = ((Peer) manager).getParentService(); + if (parentService instanceof ProxyService) + { + proxy.setDaemonPool(((ProxyService) parentService).getDaemonPool()); + } + } + response.setResult(String.valueOf(uri)); + } + } + + // ----- inner class: EnsurePublisherRequest ---------------------------- + + /** + * A request to ensure a publisher. + */ + @SuppressWarnings({"unchecked"}) + public static class EnsurePublisherRequest + extends NewChannelRequest + { + /** + * The position of the URI in the response array. + */ + public static final int RESPONSE_ID_URI = 0; + + /** + * The position of the publisher identifier in the response array. + */ + public static final int RESPONSE_ID_PUBLISHER_ID = 1; + + /** + * The position of the batch size in the response array. + */ + public static final int RESPONSE_ID_BATCH_SIZE = 2; + + /** + * The position of the channel count in the response array. + */ + public static final int RESPONSE_ID_CHANNEL_COUNT = 3; + + /** + * The publisher's channel count. + */ + private int m_cChannel; + + public EnsurePublisherRequest() + { + } + + public void setChannelCount(int cChannel) + { + m_cChannel = cChannel; + } + + @Override + public int getTypeId() + { + return TYPE_ID_ENSURE_PUBLISHER; + } + + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + String sName = getTopicName(); + + if (sName == null || sName.isEmpty()) + { + throw new IllegalArgumentException("The topic name cannot be null or blank"); + } + + NamedTopic topic = service.ensureTopic(sName, null); + TopicPublisherProxy proxy = createPublisherProxy(topic); + PublisherConnector connector = proxy.getConnector(); + + URI uri = createChannel(proxy); + + ConnectionManager manager = getChannel().getConnection().getConnectionManager(); + if (manager instanceof Peer) + { + Service parentService = ((Peer) manager).getParentService(); + if (parentService instanceof ProxyService) + { + proxy.setDaemonPool(((ProxyService) parentService).getDaemonPool()); + } + } + + Object[] aoResponse = new Object[4]; + aoResponse[RESPONSE_ID_URI] = String.valueOf(uri); + aoResponse[RESPONSE_ID_PUBLISHER_ID] = connector.getId(); + aoResponse[RESPONSE_ID_BATCH_SIZE] = connector.getMaxBatchSizeBytes(); + aoResponse[RESPONSE_ID_CHANNEL_COUNT] = connector.getChannelCount(); + + response.setResult(aoResponse); + } + + /** + * Create a {@link TopicPublisherProxy}. + * + * @return a {@link TopicPublisherProxy} + */ + @SuppressWarnings("rawtypes") + protected TopicPublisherProxy createPublisherProxy(NamedTopic topic) + { + if (topic instanceof PublisherConnector.Factory) + { + Publisher.Option[] options; + if (m_cChannel <= 0) + { + options = new Publisher.Option[0]; + } + else + { + options = new Publisher.Option[]{NamedTopicPublisher.ChannelCount.of(m_cChannel)}; + } + + PublisherConnector connector = ((PublisherConnector.Factory) topic) + .createPublisherConnector(options); + + TopicPublisherProxy proxy = new TopicPublisherProxy(); + proxy.setNamedTopic(topic); + proxy.setConnector(connector); + proxy.setTransferThreshold(getTransferThreshold()); + + return proxy; + } + throw new IllegalArgumentException("The topic is not an instance of " + + PublisherConnector.Factory.class.getName()); + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_cChannel = in.readInt(10); + } + + @Override + public void writeExternal(PofWriter out) throws IOException + { + super.writeExternal(out); + out.writeInt(10, m_cChannel); + } + } + + // ----- inner class: DestroyPublisherRequest --------------------------- + + /** + * A request to destroy a publisher. + */ + public static class DestroyPublisherRequest + extends TopicServiceRequest + { + /** + * The identifier for the publisher to destroy. + */ + private int m_nPublisherId; + + public DestroyPublisherRequest() + { + super(null, null, true); + } + + @Override + public int getTypeId() + { + return TYPE_ID_DESTROY_PUBLISHER; + } + + /** + * Set the identifier for the publisher to destroy. + * + * @param nPublisherId the identifier for the publisher to destroy + * + * @throws IllegalArgumentException if the identifier specified is negative or zero. + */ + public void setPublisherId(int nPublisherId) + { + if (nPublisherId <= 0) + { + throw new IllegalArgumentException("The publisher identifier must be non-zero and positive"); + } + m_nPublisherId = nPublisherId; + } + + @Override + protected void onRun(Response response) + { + if (m_nPublisherId <= 0) + { + throw new IllegalArgumentException("The publisher identifier must be non-zero and positive"); + } + Channel channel = getChannel().getConnection().getChannel(m_nPublisherId); + if (channel != null) + { + channel.close(); + } + } + } + + // ----- inner class: EnsureSubscriberRequest --------------------------- + + /** + * A request to ensure a subscriber. + */ + @SuppressWarnings({"unchecked"}) + public static class EnsureSubscriberRequest + extends NewChannelRequest + { + /** + * The position of the URI in the response array. + */ + public static final int RESPONSE_ID_URI = 0; + + /** + * The position of the {@link com.tangosol.internal.net.topic.impl.paged.model.SubscriberId} + * in the response array. + */ + public static final int RESPONSE_ID_SUBSCRIBER_ID = 1; + + /** + * The position of the {@link com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId} + * in the response array. + */ + public static final int RESPONSE_ID_GROUP_ID = 2; + + /** + * An optional {@link Filter} to filter messages. + */ + private Filter m_filter; + + /** + * An optional {@link ValueExtractor} to convert messages. + */ + private ValueExtractor m_extractor; + + /** + * {@code true} if the subscriber should return from a receive request + * immediately if the topic is empty + */ + private boolean m_fCompleteOnEmpty; + + /** + * The subscriber group identifier. + */ + private String m_sSubscriberGroup; + + // ----- constructors --------------------------------------------------- + + public EnsureSubscriberRequest() + { + } + + @Override + public int getTypeId() + { + return TYPE_ID_ENSURE_SUBSCRIBER; + } + + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + String sName = getTopicName(); + + if (sName == null || sName.isEmpty()) + { + throw new IllegalArgumentException("The topic name cannot be null or blank"); + } + + NamedTopic topic = service.ensureTopic(sName, null); + TopicSubscriberProxy proxy = createTopicSubscriberProxy(topic); + ConnectedSubscriber subscriber = proxy.getSubscriber(); + + URI uri = createChannel(proxy); + + ConnectionManager manager = getChannel().getConnection().getConnectionManager(); + if (manager instanceof Peer) + { + Service parentService = ((Peer) manager).getParentService(); + if (parentService instanceof ProxyService) + { + proxy.setDaemonPool(((ProxyService) parentService).getDaemonPool()); + } + } + + Object[] aoResponse = new Object[3]; + aoResponse[RESPONSE_ID_URI] = String.valueOf(uri); + aoResponse[RESPONSE_ID_SUBSCRIBER_ID] = subscriber.getSubscriberId(); + aoResponse[RESPONSE_ID_GROUP_ID] = subscriber.getSubscriberGroupId(); + + response.setResult(aoResponse); + } + + @SuppressWarnings("rawtypes") + public TopicSubscriberProxy createTopicSubscriberProxy(NamedTopic topic) + { + Channel channel = getChannel(); + Connection connection = channel.getConnection(); + Member member = topic.getTopicService().getCluster().getLocalMember(); + + Subscriber.Option[] options = new Subscriber.Option[] + { + new NamedTopicFactory.SubscriberIdOption(member.getId(), member.getUuid()), + m_sSubscriberGroup != null && !m_sSubscriberGroup.isEmpty() + ? Subscriber.inGroup(m_sSubscriberGroup) + : Subscriber.Option.nullOption(), + Subscriber.withFilter(m_filter), + Subscriber.withConverter(m_extractor), + m_fCompleteOnEmpty ? Subscriber.CompleteOnEmpty.enabled() : Subscriber.Option.nullOption(), + }; + + ConnectedSubscriber subscriber + = (ConnectedSubscriber) topic.createSubscriber(options); + + TopicSubscriberProxy proxy = new TopicSubscriberProxy(); + proxy.setSubscriber(subscriber); + proxy.setNamedTopic(topic); + proxy.setTransferThreshold(getTransferThreshold()); + + ConnectionManager manager = connection.getConnectionManager(); + if (manager instanceof Peer) + { + Service parentService = ((Peer) manager).getParentService(); + if (parentService instanceof ProxyService) + { + proxy.setDaemonPool(((ProxyService) parentService).getDaemonPool()); + } + } + + return proxy; + } + + public void setFilter(Filter filter) + { + m_filter = filter; + } + + public void setExtractor(ValueExtractor extractor) + { + m_extractor = extractor; + } + + public void setCompleteOnEmpty(boolean fCompleteOnEmpty) + { + m_fCompleteOnEmpty = fCompleteOnEmpty; + } + + public void setSubscriberGroup(String sSubscriberGroup) + { + m_sSubscriberGroup = sSubscriberGroup; + } + + @Override + public void readExternal(PofReader in) throws IOException + { + super.readExternal(in); + m_filter = in.readObject(10); + m_extractor = in.readObject(11); + m_fCompleteOnEmpty = in.readBoolean(12); + m_sSubscriberGroup = in.readString(13); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeObject(10, m_filter); + out.writeObject(11, m_extractor); + out.writeBoolean(12, m_fCompleteOnEmpty); + out.writeString(13, m_sSubscriberGroup); + } + } + + // ----- inner class: DestroySubscriberRequest -------------------------- + + /** + * A request to destroy a subscriber. + */ + public static class DestroySubscriberRequest + extends TopicServiceRequest + { + /** + * The identifier for the subscriber to destroy. + */ + private int m_nSubscriberId; + + public DestroySubscriberRequest() + { + super(null, null, true); + } + + @Override + public int getTypeId() + { + return TYPE_ID_DESTROY_SUBSCRIBER; + } + + /** + * Get the identifier for the subscriber to destroy. + * + * @return the identifier for the subscriber to destroy + */ + public int getSubscriberId() + { + return m_nSubscriberId; + } + + /** + * Set the identifier for the subscriber to destroy. + * + * @param nSubscriberId the identifier for the subscriber to destroy + * + * @throws IllegalArgumentException if the identifier specified is negative or zero. + */ + public void setSubscriberId(int nSubscriberId) + { + if (nSubscriberId <= 0) + { + throw new IllegalArgumentException("The subscriber identifier must be non-zero and positive"); + } + m_nSubscriberId = nSubscriberId; + } + + @Override + protected void onRun(Response response) + { + if (m_nSubscriberId <= 0) + { + throw new IllegalArgumentException("The subscriber identifier must be non-zero and positive"); + } + Channel channel = getChannel().getConnection().getChannel(m_nSubscriberId); + if (channel != null) + { + channel.close(); + } + } + } + + // ----- GetSubscriberGroupsRequest ------------------------------------- + + /** + * A request to get a subscriber group. + */ + public static class GetSubscriberGroupsRequest + extends TopicServiceRequest + { + public GetSubscriberGroupsRequest() + { + super(null, null, true); + } + + @Override + public int getTypeId() + { + return TYPE_ID_GET_SUBSCRIBER_GROUPS; + } + + /** + * Called when the Request is run. + * + * @param response the Response that should be populated with the + * result of running the Request + */ + @Override + protected void onRun(Response response) + { + TopicService service = getTopicService(); + _assert(service != null); + //noinspection DataFlowIssue + response.setResult(service.getSubscriberGroups(getTopicName())); + } + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/NamedTopicProtocol.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/NamedTopicProtocol.java new file mode 100644 index 0000000000000..253dac154cc25 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/NamedTopicProtocol.java @@ -0,0 +1,160 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol + +package com.tangosol.coherence.component.net.extend.protocol; + +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; + +/** + * The Extend protocol for a {@link com.tangosol.net.topic.NamedTopic}. + * + * @author Jonathan Knight 2024.11.26 + */ +public class NamedTopicProtocol + extends com.tangosol.coherence.component.net.extend.Protocol + { + // ---- Fields declarations ---- + + /** + * Property Instance + */ + private static NamedTopicProtocol __s_Instance; + + private static void _initStatic$Default() + { + } + + // Static initializer (from _initStatic) + static + { + _initStatic$Default(); + + setInstance(new NamedTopicProtocol()); + } + + // Default constructor + public NamedTopicProtocol() + { + this(null, null, true); + } + + // Initializing constructor + public NamedTopicProtocol(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + // Main initializer + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setVersionCurrent(1); + setVersionSupported(1); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + // Private initializer + protected void __initPrivate() + { + + super.__initPrivate(); + } + + //++ getter for static property _Instance + + /** + * Getter for property _Instance.

+ * Auto generated + */ + public static com.tangosol.coherence.Component get_Instance() + { + return new NamedTopicProtocol(); + } + + //++ getter for static property _CLASS + + /** + * Getter for property _CLASS.

+ * Property with auto-generated accessor that returns the Class object for a + * given component. + */ + public static Class get_CLASS() + { + return NamedTopicProtocol.class; + } + + //++ getter for autogen property _Module + + /** + * This is an auto-generated method that returns the global [design time] + * parent component. + *

+ * Note: the class generator will ignore any custom implementation for this + * behavior. + */ + private com.tangosol.coherence.Component get_Module() + { + return this; + } + + // Accessor for the property "Instance" + + /** + * Getter for property Instance.

+ */ + public static NamedTopicProtocol getInstance() + { + return __s_Instance; + } + + // Declared at the super level + + /** + * Instantiate a new MessageFactory for the given version of this Protocol. + * + * @param nVersion the version of the Protocol that the returned + * MessageFactory will use + * @return a new MessageFactory for the given version of this Protocol + */ + protected MessageFactory instantiateMessageFactory(int nVersion) + { + // import Component.Net.Extend.MessageFactory.NamedTopicFactory; + + return new NamedTopicFactory(); + } + + // Accessor for the property "Instance" + + /** + * Setter for property Instance.

+ */ + public static void setInstance(NamedTopicProtocol protocolInstance) + { + __s_Instance = protocolInstance; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/TopicServiceProtocol.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/TopicServiceProtocol.java new file mode 100644 index 0000000000000..4b78ab2912bca --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/protocol/TopicServiceProtocol.java @@ -0,0 +1,113 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.protocol.TopicServiceProtocol + +package com.tangosol.coherence.component.net.extend.protocol; + +import com.tangosol.coherence.component.net.extend.messageFactory.TopicServiceFactory; + +/** + * The Extend protocol for a {@link com.tangosol.net.TopicService}. + * + * @author Jonathan Knight 2024.11.26 + */ +public class TopicServiceProtocol + extends com.tangosol.coherence.component.net.extend.Protocol + { + /** + * Singleton instance + */ + private static final TopicServiceProtocol __s_Instance = new TopicServiceProtocol(); + + /** + * Property VERSION + */ + public static final int VERSION = 1; + + public TopicServiceProtocol() + { + this(null, null, true); + } + + public TopicServiceProtocol(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setVersionCurrent(VERSION); + setVersionSupported(VERSION); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + /** + * Getter for property Instance.

+ */ + public static TopicServiceProtocol getInstance() + { + return __s_Instance; + } + + /** + * Instantiate a new MessageFactory for the given version of this Protocol. + * + * @param nVersion the version of the Protocol that the returned + * MessageFactory will use + * @return a new MessageFactory for the given version of this Protocol + */ + @Override + protected MessageFactory instantiateMessageFactory(int nVersion) + { + return new TopicServiceFactory(); + } + + /** + * The "component has been initialized" method-notification called out of + * setConstructed() for the topmost component and that in turn notifies all + * the children. + *

+ * This notification gets called before the control returns back to this + * component instantiator (using new Component.X() or + * _newInstance(sName)) and on the same thread. In addition, + * visual components have a "posted" notification onInitUI that + * is called after (or at the same time as) the control returns back to the + * instantiator and possibly on a different thread. + */ + @Override + public void onInit() + { + setVersionCurrent(VERSION); + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/NamedTopicProxy.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/NamedTopicProxy.java new file mode 100644 index 0000000000000..baae0445cdd83 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/NamedTopicProxy.java @@ -0,0 +1,447 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.proxy.NamedTopicProxy + +package com.tangosol.coherence.component.net.extend.proxy; + +import com.tangosol.coherence.component.net.extend.message.request.NamedTopicRequest; + +import com.tangosol.coherence.component.net.extend.message.request.TopicServiceRequest; +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; + +import com.tangosol.net.MemberEvent; +import com.tangosol.net.MemberListener; +import com.tangosol.net.PagedTopicService; +import com.tangosol.net.TopicService; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.ConnectionException; +import com.tangosol.net.messaging.Message; +import com.tangosol.net.messaging.Protocol; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.NamedTopicEvent; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.util.SynchronousListener; + +/** + * A server side Extend proxy for a {@link NamedTopic}. + * + * @author Jonathan Knight 2024.11.26 + */ +@SuppressWarnings({"rawtypes", "unchecked", "PatternVariableCanBeUsed"}) +public class NamedTopicProxy + extends com.tangosol.coherence.component.net.extend.Proxy + implements MemberListener, + Channel.Receiver, NamedTopic, com.tangosol.net.topic.NamedTopicListener, SynchronousListener + { + /** + * Property Channel + */ + private Channel m_channel; + + /** + * Property NamedTopic + */ + private NamedTopic m_topic; + + /** + * Property TransferThreshold + */ + private long m_nTransferThreshold; + + /** + * A unique identifier for this proxy. + */ + private int m_nProxyId; + + public NamedTopicProxy() + { + super(null, null, false); + __init(); + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setEnabled(true); + setTransferThreshold(524288L); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + @Override + public void destroySubscriberGroup(String Param_1) + { + getNamedTopic().destroySubscriberGroup(Param_1); + } + + @Override + public void ensureSubscriberGroup(String sName) + { + getNamedTopic().ensureSubscriberGroup(sName); + } + + @Override + @SuppressWarnings("unchecked") + public void ensureSubscriberGroup(String Param_1, com.tangosol.util.Filter Param_2, com.tangosol.util.ValueExtractor Param_3) + { + getNamedTopic().ensureSubscriberGroup(Param_1, Param_2, Param_3); + } + + @Override + public int getChannelCount() + { + return getNamedTopic().getChannelCount(); + } + + @Override + public int getRemainingMessages(String sSubscriberGroup) + { + return getNamedTopic().getRemainingMessages(sSubscriberGroup); + } + + @Override + public int getRemainingMessages(String Param_1, int[] Param_2) + { + return getNamedTopic().getRemainingMessages(Param_1, Param_2); + } + + @Override + public java.util.Set getSubscriberGroups() + { + return getNamedTopic().getSubscriberGroups(); + } + + @Override + public TopicService getTopicService() + { + return getNamedTopic().getTopicService(); + } + + @Override + public void addListener(com.tangosol.net.topic.NamedTopicListener listener) + { + getNamedTopic().addListener(listener); + } + + @Override + public void close() + { + // must close the NamedTopic via the TopicServiceProxy + throw new UnsupportedOperationException(); + } + + protected void closeChannel() + { + Channel channel = getChannel(); + if (channel != null) + { + channel.close(); + } + } + + @Override + public Publisher createPublisher(Publisher.Option[] options) + { + throw new UnsupportedOperationException(); + } + + @Override + public Subscriber createSubscriber(Subscriber.Option[] options) + { + throw new UnsupportedOperationException(); + } + + @Override + public void destroy() + { + // must destroy the NamedTopic via the TopicServiceProxy + throw new UnsupportedOperationException(); + } + + /** + * Getter for property Channel.

+ */ + public Channel getChannel() + { + return m_channel; + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + String sTopicName = null; + String sServiceName = null; + + try + { + sTopicName = getName(); + sServiceName = getTopicService().getInfo().getServiceName(); + } + catch (Throwable t) + { + // ignored + } + return "NamedTopic=" + (sTopicName == null ? "N/A" : sTopicName) + + ", Service=" + (sServiceName == null ? "N/A" : sServiceName); + } + + @Override + public String getName() + { + return m_topic.getName(); + } + + /** + * Getter for property NamedTopic.

+ */ + public NamedTopic getNamedTopic() + { + return m_topic; + } + + @Override + public Protocol getProtocol() + { + return NamedTopicProtocol.getInstance(); + } + + @Override + public com.tangosol.net.Service getService() + { + return null; + } + + /** + * Getter for property TransferThreshold.

+ */ + public long getTransferThreshold() + { + return m_nTransferThreshold; + } + + @Override + public boolean isActive() + { + return false; + } + + @Override + public void memberJoined(MemberEvent evt) + { + } + + @Override + public void memberLeaving(MemberEvent evt) + { + } + + @Override + public void memberLeft(MemberEvent evt) + { + Channel channel = getChannel(); + + if (channel != null) + { + // we only add a member listener if the service is a PagedTopicService + PagedTopicService service = (PagedTopicService) evt.getService(); + // avoid iterating the member set (getOwnershipSenior()) if partition 0 has an assignment + if (service.getPartitionOwner(0) == null && service.getOwnershipSenior() == null) + { + Protocol.MessageFactory factory = channel.getMessageFactory(); +// ToDo this should send a message to indicate all subscriber channels lost +// com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory.NoStorageMembers message = (com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory.NoStorageMembers) factory.createMessage(com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory.NoStorageMembers.TYPE_ID); +// channel.send(message); + } + } + } + + protected void onDeactivation() + { + if (getTopicService().isRunning()) + { + closeChannel(); + } + } + + @Override + public void onEvent(com.tangosol.net.topic.NamedTopicEvent event) + { + if (event.getType() == NamedTopicEvent.Type.Destroyed) + { + onDeactivation(); + } + + Channel channel = getChannel(); + Message[] aMessages = instantiateEventMessages(event); + try + { + for (Message message : aMessages) + { + channel.send(message); + } + } + catch (ConnectionException e) + { + // the Channel is most likely closing or has been closed + } + catch (Throwable t) + { + _trace(t, "Error sending topic event to " + channel); + } + } + + private Message[] instantiateEventMessages(NamedTopicEvent event) + { + // we only have a destroy event which is handled by the channel closing + if (event.getType() == NamedTopicEvent.Type.Destroyed) + { + Channel channel = getChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + Message message = factory.createMessage(NamedTopicFactory.TYPE_ID_DESTROY_EVENT); + return new Message[]{message}; + } + return new Message[0]; + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + if (message instanceof TopicServiceRequest) + { + TopicServiceRequest request = (TopicServiceRequest) message; + request.setTopicName(m_topic.getName()); + request.setTopicService(m_topic.getTopicService()); + request.setTransferThreshold(getTransferThreshold()); + } + if (message instanceof NamedTopicRequest) + { + NamedTopicRequest request = (NamedTopicRequest) message; + request.setNamedTopic(m_topic); + request.setTransferThreshold(getTransferThreshold()); + } + message.run(); + } + + @Override + public void registerChannel(Channel channel) + { + _assert(getChannel() == null); + + NamedTopic topic = getNamedTopic(); + _assert(topic != null); + + setChannel(channel); + + //noinspection DataFlowIssue + topic.addListener(this); + + TopicService service = topic.getTopicService(); + if (service instanceof PagedTopicService && + !((PagedTopicService) service).isLocalStorageEnabled()) + { + service.addMemberListener(this); + } + } + + @Override + public void release() + { + // must release the NamedTopic via the TopicServiceProxy + throw new UnsupportedOperationException(); + } + + public void removeListener(com.tangosol.net.topic.NamedTopicListener listener) + { + throw new UnsupportedOperationException(); + } + + /** + * Setter for property Channel.

+ */ + public void setChannel(Channel channel) + { + m_channel = channel; + } + + /** + * Setter for property NamedTopic.

+ */ + public void setNamedTopic(NamedTopic topic) + { + m_topic = topic; + } + + /** + * Setter for property TransferThreshold.

+ */ + public void setTransferThreshold(long lThreshold) + { + m_nTransferThreshold = lThreshold; + } + + + /** + * Return this proxy's identifier. + * + * @return this proxy's identifier + */ + public int getProxyId() + { + return m_nProxyId; + } + + /** + * Set this proxy's identifier. + * + * @param nProxyId this proxy's identifier + */ + public void setProxyId(int nProxyId) + { + m_nProxyId = nProxyId; + } + + @Override + public void unregisterChannel(Channel channel) + { + _assert(getChannel() == channel); + NamedTopic topic = getNamedTopic(); + _assert(topic != null); + //noinspection DataFlowIssue + topic.removeListener(this); + setChannel(null); + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicPublisherProxy.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicPublisherProxy.java new file mode 100644 index 0000000000000..237be51e8c7ab --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicPublisherProxy.java @@ -0,0 +1,547 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.proxy.NamedTopicProxy + +package com.tangosol.coherence.component.net.extend.proxy; + +import com.tangosol.coherence.component.net.extend.message.request.NamedTopicRequest; +import com.tangosol.coherence.component.net.extend.message.request.TopicPublisherRequest; + +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; + +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; + +import com.tangosol.internal.net.topic.NamedTopicPublisher; +import com.tangosol.internal.net.topic.PublishResult; +import com.tangosol.internal.net.topic.PublisherChannelConnector; +import com.tangosol.internal.net.topic.PublisherConnector; + +import com.tangosol.net.MemberEvent; +import com.tangosol.net.MemberListener; +import com.tangosol.net.PagedTopicService; +import com.tangosol.net.TopicService; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.ConnectionException; +import com.tangosol.net.messaging.Message; +import com.tangosol.net.messaging.Protocol; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Publisher; + +import com.tangosol.net.topic.TopicDependencies; +import com.tangosol.util.Binary; +import com.tangosol.util.Converter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import java.util.function.BiConsumer; + +/** + * A server side proxy for a topic {@link Publisher}. + * + * @author Jonathan Knight 2024.11.26 + */ +@SuppressWarnings("PatternVariableCanBeUsed") +public class TopicPublisherProxy + extends com.tangosol.coherence.component.net.extend.Proxy + implements MemberListener, Channel.Receiver, NamedTopicPublisher.PublisherListener + { + /** + * Property Channel + */ + private Channel m_channel; + + /** + * The named topic. + */ + private transient NamedTopic m_topic; + + /** + * The converter to use if the client is using a different serializer ot the server. + */ + private Converter m_converter; + + /** + * The {@link PublisherConnector} this proxy represents. + */ + private PublisherConnector m_connector; + + /** + * The list of channel publishers. + */ + private final Map> m_channels = new HashMap<>(); + + /** + * The lock. + */ + private final Lock f_lock = new ReentrantLock(); + + /** + * Property TransferThreshold + */ + private long m_nTransferThreshold; + + /** + * A unique identifier for this proxy. + */ + private int m_nProxyId; + + public TopicPublisherProxy() + { + super(null, null, false); + __init(); + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setEnabled(true); + setTransferThreshold(524288L); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + // ----- proxy methods -------------------------------------------------- + + /** + * Return this proxy's identifier. + * + * @return this proxy's identifier + */ + public int getProxyId() + { + return m_nProxyId; + } + + /** + * Set this proxy's identifier. + * + * @param nProxyId this proxy's identifier + */ + public void setProxyId(int nProxyId) + { + m_nProxyId = nProxyId; + } + + protected void closeChannel() + { + Channel channel = getChannel(); + if (channel != null) + { + channel.close(); + } + } + + /** + * Getter for property Channel.

+ */ + public Channel getChannel() + { + return m_channel; + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + String sTopicName = null; + String sServiceName = null; + + try + { + sTopicName = m_topic.getName(); + sServiceName = m_connector.getTopicName(); + } + catch (Throwable t) + { + // ignored + } + return "NamedTopic=" + (sTopicName == null ? "N/A" : sTopicName) + + ", Publisher=" + m_connector.getId() + + ", Service=" + (sServiceName == null ? "N/A" : sServiceName); + } + + @Override + public String getName() + { + return m_topic.getName() + "#" + m_connector.getId(); + } + + @Override + public Protocol getProtocol() + { + return NamedTopicProtocol.getInstance(); + } + + /** + * Getter for property TransferThreshold.

+ */ + public long getTransferThreshold() + { + return m_nTransferThreshold; + } + + @Override + public void memberJoined(MemberEvent evt) + { + } + + @Override + public void memberLeaving(MemberEvent evt) + { + } + + @Override + public void memberLeft(MemberEvent evt) + { + Channel channel = getChannel(); + + if (channel != null) + { + // we only add a member listener if the service is a PagedTopicService + PagedTopicService service = (PagedTopicService) evt.getService(); + // avoid iterating the member set (getOwnershipSenior()) if partition 0 has an assignment + if (service.getPartitionOwner(0) == null && service.getOwnershipSenior() == null) + { + Protocol.MessageFactory factory = channel.getMessageFactory(); +// ToDo this should send a message to indicate all subscriber channels lost +// com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory.NoStorageMembers message = (com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory.NoStorageMembers) factory.createMessage(com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory.NoStorageMembers.TYPE_ID); +// channel.send(message); + } + } + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + if (message instanceof NamedTopicRequest) + { + NamedTopicRequest request = (NamedTopicRequest) message; + request.setNamedTopic(m_topic); + request.setTransferThreshold(getTransferThreshold()); + } + if (message instanceof TopicPublisherRequest) + { + TopicPublisherRequest request = (TopicPublisherRequest) message; + request.setPublisherConnector(this); + } + message.run(); + } + + @Override + public void registerChannel(Channel channel) + { + _assert(getChannel() == null); + + NamedTopic topic = m_topic; + _assert(topic != null); + + setChannel(channel); + + //noinspection DataFlowIssue + TopicService service = topic.getTopicService(); + if (service instanceof PagedTopicService && + !((PagedTopicService) service).isLocalStorageEnabled()) + { + service.addMemberListener(this); + } + } + + @Override + public void onEvent(NamedTopicPublisher.PublisherEvent event) + { + Channel channel = getChannel(); + Message message = instantiateEventMessage(event); + if (message != null) + { + try + { + channel.send(message); + } + catch (ConnectionException e) + { + // the Channel is most likely closing or has been closed + } + catch (Throwable t) + { + _trace(t, "Error sending MapEvent to " + channel); + } + } + } + + private Message instantiateEventMessage(NamedTopicPublisher.PublisherEvent event) + { + NamedTopicPublisher.PublisherEvent.Type type = event.getType(); + Channel channel = getChannel(); + + NamedTopicFactory.PublisherEvent message = (NamedTopicFactory.PublisherEvent) + channel.getMessageFactory().createMessage(NamedTopicFactory.TYPE_ID_PUBLISHER_EVENT); + + message.setType(type); + + switch (type) + { + case Disconnected: + break; + case Released: + break; + case Destroyed: + break; + case ChannelsFreed: + message.setChannels(event.getChannels()); + return message; + case Connected: + break; + default: + return null; + } + return message; + } + + /** + * Setter for property Channel.

+ */ + public void setChannel(Channel channel) + { + m_channel = channel; + } + + /** + * Set the {@link PublisherConnector} this proxy represents. + */ + public void setConnector(PublisherConnector connector) + { + PublisherConnector oldConnector = m_connector; + if (oldConnector != null) + { + oldConnector.removeListener(this); + } + m_connector = connector; + if (connector != null) + { + connector.addListener(this); + } + } + + /** + * Return the {@link PublisherConnector}. + * + * @return the {@link PublisherConnector} + */ + public PublisherConnector getConnector() + { + return m_connector; + } + + /** + * Set the converter to use. + * + * @param converter the converter to use + */ + public void setConverter(Converter converter) + { + m_converter = converter; + } + + /** + * Return the converter to use. + * + * @return the converter to use + */ + public Converter getConverter() + { + return m_converter == null ? bin -> bin : m_converter; + } + + /** + * Setter for property TransferThreshold.

+ */ + public void setTransferThreshold(long lThreshold) + { + m_nTransferThreshold = lThreshold; + } + + public void setNamedTopic(NamedTopic topic) + { + m_topic = topic; + } + + @Override + public void unregisterChannel(Channel channel) + { + _assert(getChannel() == channel); + m_connector.close(); + m_connector.removeListener(this); + + setChannel(null); + } + + /** + * Return the channel connector, creating a new one if necessary. + * + * @param nChannel the channel to obtain a connector for + * + * @return the connector for the specified channel + */ + public PublisherChannelConnector getChannelConnector(int nChannel) + { + PublisherChannelConnector connector = m_channels.size() > nChannel + ? m_channels.get(nChannel) : null; + + if (connector == null) + { + f_lock.lock(); + try + { + connector = m_channels.computeIfAbsent(nChannel, k -> new ChannelConnector(m_connector.createChannelConnector(nChannel))); + } + finally + { + f_lock.unlock(); + } + } + return connector; + } + + // ----- inner class: ChannelConnector ---------------------------------- + + protected static class ChannelConnector + implements PublisherChannelConnector + { + protected final PublisherChannelConnector m_channelConnector; + + public ChannelConnector(PublisherChannelConnector connector) + { + m_channelConnector = connector; + } + + @Override + public boolean isActive() + { + return m_channelConnector.isActive(); + } + + @Override + public int getChannel() + { + return m_channelConnector.getChannel(); + } + + @Override + public void close() + { + m_channelConnector.close(); + } + + @Override + public String getTopicName() + { + return m_channelConnector.getTopicName(); + } + + @Override + public void ensureConnected() + { + m_channelConnector.ensureConnected(); + } + + @Override + public CompletionStage initialize() + { + return m_channelConnector.initialize(); + } + + @Override + public void offer(Object oCookie, List listBinary, int nNotifyPostFull, BiConsumer handler) + { + m_channelConnector.initialize().handle((o, error) -> + { + if (error == null) + { + try + { + m_channelConnector.offer(o, listBinary, nNotifyPostFull, (result, err) -> + { + if (err == null && result.getStatus() == PublishResult.Status.Retry) + { + m_channelConnector.prepareOfferRetry(result.getRetryCookie()) + .handle((ignored, throwable) -> + { + handler.accept(result, throwable); + return null; + }); + } + else + { + handler.accept(result, err); + } + }); + } + catch (Throwable t) + { + handler.accept(null, t); + } + } + else + { + handler.accept(null, error); + } + return null; + }); + } + + @Override + public CompletionStage prepareOfferRetry(Object oCookie) + { + return m_channelConnector.prepareOfferRetry(oCookie); + } + + @Override + public TopicDependencies getTopicDependencies() + { + return m_channelConnector.getTopicDependencies(); + } + + @Override + public TopicService getTopicService() + { + return m_channelConnector.getTopicService(); + } + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicSubscriberProxy.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicSubscriberProxy.java new file mode 100644 index 0000000000000..09a82d6f3ad0d --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/TopicSubscriberProxy.java @@ -0,0 +1,382 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.proxy.NamedTopicProxy + +package com.tangosol.coherence.component.net.extend.proxy; + +import com.oracle.coherence.common.base.Logger; +import com.tangosol.coherence.component.net.extend.message.request.NamedTopicRequest; +import com.tangosol.coherence.component.net.extend.message.request.TopicSubscriberRequest; + +import com.tangosol.coherence.component.net.extend.messageFactory.NamedTopicFactory; + +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; + +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.internal.net.topic.SubscriberConnector.SubscriberEvent; + +import com.tangosol.net.MemberEvent; +import com.tangosol.net.MemberListener; +import com.tangosol.net.PagedTopicService; +import com.tangosol.net.TopicService; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.ConnectionException; +import com.tangosol.net.messaging.Protocol; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.NamedTopicEvent; +import com.tangosol.net.topic.NamedTopicListener; +import com.tangosol.net.topic.Subscriber; + +import com.tangosol.util.Binary; + +/** + * A server side proxy for a topic {@link Subscriber}. + * + * @author Jonathan Knight 2024.11.26 + */ +@SuppressWarnings({"rawtypes", "PatternVariableCanBeUsed"}) +public class TopicSubscriberProxy + extends com.tangosol.coherence.component.net.extend.Proxy + implements MemberListener, Channel.Receiver, NamedTopicListener, SubscriberConnector.SubscriberListener + { + /** + * Property Channel + */ + private com.tangosol.net.messaging.Channel m_channel; + + /** + * The named topic. + */ + private transient NamedTopic m_topic; + + /** + * The proxied subscriber; + */ + private SubscriberConnector.ConnectedSubscriber m_subscriber; + + /** + * Property TransferThreshold + */ + private long m_transferThreshold; + + /** + * A unique identifier for this proxy. + */ + private int m_nProxyId; + + // ----- constructors --------------------------------------------------- + + public TopicSubscriberProxy() + { + super(null, null, false); + __init(); + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setEnabled(true); + setTransferThreshold(524288L); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + // ----- proxy methods -------------------------------------------------- + + protected void closeChannel() + { + com.tangosol.net.messaging.Channel channel = getChannel(); + if (channel != null) + { + channel.close(); + } + } + + /** + * Getter for property Channel.

+ */ + public com.tangosol.net.messaging.Channel getChannel() + { + return m_channel; + } + + /** + * Return a human-readable description of this component. + * + * @return a String representation of this component + */ + @Override + protected String getDescription() + { + String sTopicName = null; + String sServiceName = null; + + try + { + sTopicName = m_topic.getName(); + sServiceName = m_topic.getTopicService().getInfo().getServiceName(); + } + catch (Throwable t) + { + // ignored + } + return "NamedTopic=" + (sTopicName == null ? "N/A" : sTopicName) + + ", Subscriber=" + m_subscriber.getSubscriberId() + + ", Service=" + (sServiceName == null ? "N/A" : sServiceName); + } + + @Override + public String getName() + { + return m_topic.getName() + "#" + m_subscriber.getSubscriberId(); + } + + @Override + public Protocol getProtocol() + { + return NamedTopicProtocol.getInstance(); + } + + /** + * Getter for property TransferThreshold.

+ */ + public long getTransferThreshold() + { + return m_transferThreshold; + } + + @Override + public void memberJoined(MemberEvent evt) + { + } + + @Override + public void memberLeaving(MemberEvent evt) + { + } + + @Override + public void memberLeft(MemberEvent evt) + { + com.tangosol.net.messaging.Channel channel = getChannel(); + if (channel != null) + { + // we only add a member listener if the service is a PagedTopicService + PagedTopicService service = (PagedTopicService) evt.getService(); + // avoid iterating the member set (getOwnershipSenior()) if partition 0 has an assignment + if (service.getPartitionOwner(0) == null && service.getOwnershipSenior() == null) + { + onEvent(new SubscriberEvent(m_subscriber.getConnector(), SubscriberEvent.Type.ChannelAllocation)); + } + } + } + + protected void onDeactivation() + { + TopicService service = m_topic.getTopicService(); + if (service.isRunning()) + { + closeChannel(); + } + } + + @Override + public void onEvent(NamedTopicEvent event) + { + if (event.getType() == NamedTopicEvent.Type.Destroyed) + { + onDeactivation(); + } + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + if (message instanceof NamedTopicRequest) + { + NamedTopicRequest request = (NamedTopicRequest) message; + request.setNamedTopic(m_topic); + request.setTransferThreshold(getTransferThreshold()); + } + if (message instanceof TopicSubscriberRequest) + { + TopicSubscriberRequest request = (TopicSubscriberRequest) message; + request.setSubscriber(m_subscriber); + } + message.run(); + } + + @Override + public void registerChannel(com.tangosol.net.messaging.Channel channel) + { + _assert(getChannel() == null); + + NamedTopic topic = m_topic; + _assert(topic != null); + + setChannel(channel); + + //noinspection DataFlowIssue + topic.addListener(this); + + TopicService service = topic.getTopicService(); + if (service instanceof PagedTopicService && + !((PagedTopicService) service).isLocalStorageEnabled()) + { + service.addMemberListener(this); + } + } + + @Override + public void onEvent(SubscriberEvent evt) + { + Channel channel = getChannel(); + if (channel != null) + { + NamedTopicFactory.SubscriberChannelEvent message = (NamedTopicFactory.SubscriberChannelEvent) + channel.getMessageFactory().createMessage(NamedTopicFactory.TYPE_ID_SUBSCRIBER_EVENT); + + message.setType(evt.getType().ordinal()); + message.setPopulatedChannels(evt.getPopulatedChannels()); + message.setAllocatedChannels(evt.getAllocatedChannels()); + + try + { + channel.send(message); + } + catch (ConnectionException e) + { + // the Channel is most likely closing or has been closed + } + catch (Throwable t) + { + _trace(t, "Error sending SubscriberEvent to " + channel); + } + } + } + + /** + * Setter for property Channel.

+ */ + public void setChannel(com.tangosol.net.messaging.Channel channel) + { + m_channel = channel; + } + + /** + * Return this proxy's identifier. + * + * @return this proxy's identifier + */ + public int getProxyId() + { + return m_nProxyId; + } + + /** + * Set this proxy's identifier. + * + * @param nProxyId this proxy's identifier + */ + public void setProxyId(int nProxyId) + { + m_nProxyId = nProxyId; + } + + /** + * Set the proxied subscriber. + * + * @param subscriber the proxied subscriber + */ + public void setSubscriber(SubscriberConnector.ConnectedSubscriber subscriber) + { + if (m_subscriber != null) + { + m_subscriber.getConnector().removeListener(this); + } + m_subscriber = subscriber; + if (m_subscriber != null) + { + m_subscriber.getConnector().addListener(this); + } + } + + /** + * Return the proxied subscriber. + * + * @return the proxied subscriber + */ + public SubscriberConnector.ConnectedSubscriber getSubscriber() + { + return m_subscriber; + } + + /** + * Set the {@link NamedTopic} this proxy is subscribing to. + * + * @param topic the {@link NamedTopic} this proxy is subscribing to + */ + public void setNamedTopic(NamedTopic topic) + { + m_topic = topic; + } + + /** + * Setter for property TransferThreshold.

+ */ + public void setTransferThreshold(long lThreshold) + { + m_transferThreshold = lThreshold; + } + + @Override + public void unregisterChannel(com.tangosol.net.messaging.Channel channel) + { + if (getChannel() != channel) + { + System.err.println(); + } + _assert(getChannel() == channel); + NamedTopic topic = m_topic; + if (topic != null) + { + topic.removeListener(this); + m_topic = null; + } + SubscriberConnector.ConnectedSubscriber subscriber = m_subscriber; + if (subscriber != null) + { + Logger.fine("Remote channel closed, closing remote subscriber " + subscriber.getSubscriberId()); + subscriber.close(); + m_subscriber = null; + } + setChannel(null); + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/serviceProxy/TopicServiceProxy.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/serviceProxy/TopicServiceProxy.java new file mode 100644 index 0000000000000..6dce692bac1ea --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/proxy/serviceProxy/TopicServiceProxy.java @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: Component.net.extend.proxy.serviceProxy.TopicServiceProxy + +package com.tangosol.coherence.component.net.extend.proxy.serviceProxy; + +import com.oracle.coherence.common.base.Classes; +import com.oracle.coherence.common.base.Logger; + +import com.tangosol.coherence.Component; + +import com.tangosol.coherence.component.net.extend.message.request.TopicServiceRequest; +import com.tangosol.coherence.component.net.extend.protocol.TopicServiceProtocol; +import com.tangosol.coherence.component.net.extend.proxy.ServiceProxy; + +import com.tangosol.coherence.component.util.Converter; +import com.tangosol.coherence.component.util.SafeNamedTopic; + +import com.tangosol.coherence.config.ResolvableParameterList; + +import com.tangosol.coherence.config.builder.InstanceBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.expression.NullParameterResolver; + +import com.tangosol.config.expression.Parameter; + +import com.tangosol.internal.net.service.extend.proxy.DefaultProxyDependencies; +import com.tangosol.internal.net.service.extend.proxy.DefaultTopicServiceProxyDependencies; +import com.tangosol.internal.net.service.extend.proxy.LegacyXmlTopicServiceProxyHelper; +import com.tangosol.internal.net.service.extend.proxy.ProxyDependencies; +import com.tangosol.internal.net.service.extend.proxy.TopicServiceProxyDependencies; + +import com.tangosol.internal.net.topic.ConverterConnectedSubscriber; +import com.tangosol.internal.net.topic.ConverterNamedTopic; +import com.tangosol.internal.net.topic.ConverterPublisher; +import com.tangosol.internal.net.topic.ConverterPublisherConnector; +import com.tangosol.internal.net.topic.NamedTopicPublisher; +import com.tangosol.internal.net.topic.NamedTopicSubscriber; +import com.tangosol.internal.net.topic.PublisherConnector; + +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; + +import com.tangosol.io.Serializer; + +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.TopicService; + +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.net.topic.TopicBackingMapManager; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Binary; +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.NullImplementation; +import com.tangosol.util.SafeHashSet; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * A server side proxy for a {@link TopicService}. + * + * @author Jonathan Knight 2024.11.26 + */ +@SuppressWarnings({"rawtypes", "PatternVariableCanBeUsed"}) +public class TopicServiceProxy + extends ServiceProxy + implements TopicService, Channel.Receiver + { + /** + * The set of topics created by this proxy. + */ + private Set __m_NamedTopicSet; + + /** + * The cache factory that created this service. + */ + private ConfigurableCacheFactory __m_CacheFactory; + + /** + * Property TopicService + */ + private TopicService __m_TopicService; + + /** + * Property TransferThreshold + */ + private long __m_TransferThreshold; + + /** + * True iff binary pass-through optimizations are enabled. + */ + private boolean __m_PassThroughEnabled; + + /** + * @see TopicService#getTopicBackingMapManager() + */ + private TopicBackingMapManager __m_BackingMapManager; + + /** + * Child ConverterFromBinary instance. + */ + private ConverterFromBinary __m_ConverterFromBinary; + + /** + * Child ConverterToBinary instance. + */ + private ConverterToBinary __m_ConverterToBinary; + + /** + * A Map of topic names created by this TopicServiceProxy. + *

+ * This is used by the ensureCache() method to determine if a warning should be + * logged if cache does not support pass-through optimizations. + */ + @SuppressWarnings("unchecked") + private final Set __m_NamedCacheSet = new SafeHashSet(); + + /** + * The channel being used. + */ + private Channel m_channel; + + public TopicServiceProxy() + { + this(null, null, true); + } + + public TopicServiceProxy(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + @Override + @SuppressWarnings("unchecked") + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setEnabled(true); + setNamedTopicSet(new SafeHashSet()); + setPassThroughEnabled(true); + setServiceVersion("14"); + setTransferThreshold(524288L); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + @Override + public void onInit() + { + setConverterFromBinary(new ConverterFromBinary()); + setConverterToBinary(new ConverterToBinary()); + super.onInit(); + } + + /** + * Create a new Default dependencies object by cloning the input + * dependencies. Each class or component that uses dependencies implements + * a Default dependencies class which provides the clone functionality. + * The dependency injection design pattern requires every component in the + * component hierarchy to implement clone. + * + * @return DefaultProxyDependencies the cloned dependencies + */ + @Override + protected DefaultProxyDependencies cloneDependencies(ProxyDependencies deps) + { + return new DefaultTopicServiceProxyDependencies((TopicServiceProxyDependencies) deps); + } + + @Override + public void destroyTopic(NamedTopic topic) + { + releaseTopic(topic, /*fDestroy*/ true); + } + + @Override + public int ensureChannelCount(String sTopic, int cChannel) + { + return ensureChannelCount(sTopic, cChannel, cChannel); + } + + @Override + public int ensureChannelCount(String sTopic, int cRequired, int cChannel) + { + //noinspection resource + return ensureTopic(sTopic, null).getTopicService().ensureChannelCount(sTopic, cRequired, cChannel); + } + + @SuppressWarnings("unchecked") + @Override + public NamedTopic ensureTopic(String sTopic, ClassLoader loader) + { + ConfigurableCacheFactory ccf = getCacheFactory(); + + if (!isPassThroughEnabled()) + { + return ccf.ensureTopic(sTopic, loader); + } + + ClassLoader loaderInternal = NullImplementation.getClassLoader(); + NamedTopic topic = ccf.ensureTopic(sTopic, loaderInternal); + + // check to see if the Serializer associated with the "backdoor" NamedTopic is + // compatible with the Serializer associated with this TopicServiceProxy; if + // they are not, replace the "backdoor" NamedTopic with the "front door" + // NamedTopic and wrap it with a ConverterNamedTopic that uses this + // TopicServiceProxy's Converters (see ConverterFromBinary and ConverterToBinary) + Serializer serializerThis = getSerializer(); + Serializer serializerThat = getSerializer(topic); + if (!ExternalizableHelper.isSerializerCompatible(serializerThis, serializerThat)) + { + // COH-8758 + // We cannot release the cache obtained with the NullImplementation loader + // as this will clear local caches (or caches backed by a local cache, such + // as a wrapper or converter cache of a local cache). The downside of this + // change is that we will (at worst) have one unused cache reference per + // configured cache service. The upside is that it will make subsequent + // calls of this method (with the same cache name) more efficient. + // ccf.releaseCache(cache); + NamedTopic underlying = ccf.ensureTopic(sTopic, loader); + ConverterToBinary converterToBinary = getConverterToBinary(); // object -> bin-this + ConverterFromBinary converterFromBinary = getConverterFromBinary(); // bin-this -> object + + topic = new ConverterTopic(underlying, converterToBinary, converterFromBinary, serializerThat); + + if (getNamedTopicSet().add(sTopic)) + { + if (serializerThat == null) + { + Logger.info("The topic \"" + sTopic + "\" does not support" + + " pass-through optimization for objects in" + + " internal format. If possible, consider using" + + " a different topic topology."); + } + else + { + ExternalizableHelper.reportIncompatibleSerializers(topic, "topic", getServiceName(), serializerThis); + } + } + } + + return topic; + } + + @Override + public int getChannelCount(String sTopic) + { + //noinspection resource + return ensureTopic(sTopic, null).getTopicService().getChannelCount(sTopic); + } + + @Override + public String getName() + { + return "TopicServiceProxy"; + } + + @Override + public TopicServiceProtocol getProtocol() + { + return TopicServiceProtocol.getInstance(); + } + + @Override + public String getServiceType() + { + return TopicService.TYPE_REMOTE; + } + + @Override + public Set getSubscriberGroups(String sTopic) + { + //noinspection resource + return ensureTopic(sTopic, null).getTopicService().getSubscriberGroups(sTopic); + } + + @Override + public TopicBackingMapManager getTopicBackingMapManager() + { + return __m_BackingMapManager; + } + + @Override + public void setTopicBackingMapManager(TopicBackingMapManager manager) + { + __m_BackingMapManager = manager; + } + + @Override + public Set getTopicNames() + { + Set set = __m_NamedCacheSet; + synchronized (set) + { + return new HashSet<>(set); + } + } + + @Override + protected void onDependencies(ProxyDependencies deps) + { + super.onDependencies(deps); + + TopicServiceProxyDependencies proxyDeps = (TopicServiceProxyDependencies) deps; + + // For ECCF based config, a custom service builder may be injected by CODI. + // For DCCF, we are still using the XML for custom services. + ParameterizedBuilder bldrService = proxyDeps.getServiceBuilder(); + if (bldrService == null) + { + // DCCF style + XmlElement xml = proxyDeps.getServiceClassConfig(); + if (xml != null) + { + try + { + setTopicService((TopicService) XmlHelper.createInstance(xml, + Classes.getContextClassLoader(), /*resolver*/ this, TopicService.class)); + } + catch (Exception e) + { + throw ensureRuntimeException(e); + } + } + } + else + { + // ECCF style - only an InstanceBuilder is supported + ResolvableParameterList listParams = new ResolvableParameterList(); + listParams.add(new Parameter("topic-service", this)); + + if (bldrService instanceof InstanceBuilder) + { + // Add any remaining params, skip the first param which is the service + Iterator iterParams = ((InstanceBuilder) bldrService).getConstructorParameterList().iterator(); + if (iterParams.hasNext()) + { + iterParams.next(); + } + while (iterParams.hasNext()) + { + listParams.add((Parameter) iterParams.next()); + } + } + setTopicService((TopicService) bldrService.realize(new NullParameterResolver(), + Classes.getContextClassLoader(), listParams)); + } + + setTransferThreshold(proxyDeps.getTransferThreshold()); + } + + @Override + public void onMessage(com.tangosol.net.messaging.Message message) + { + if (message instanceof TopicServiceRequest) + { + TopicServiceRequest request = (TopicServiceRequest) message; + request.setTopicService(getTopicService()); + request.setTopicServiceProxy(this); + request.setTransferThreshold(getTransferThreshold()); + } + message.run(); + } + + @Override + public void releaseTopic(NamedTopic topic) + { + releaseTopic(topic, /*fDestroy*/ false); + } + + protected void releaseTopic(NamedTopic topic, boolean fDestroy) + { + if (topic instanceof ConverterNamedTopic) + { + topic = ((ConverterNamedTopic) topic).getNamedTopic(); + } + ConfigurableCacheFactory ccf = getCacheFactory(); + if (fDestroy) + { + ccf.destroyTopic(topic); + } + else + { + ccf.releaseTopic(topic); + } + } + + @Override + public void setConfig(XmlElement xml) + { + //noinspection deprecation + setDependencies(LegacyXmlTopicServiceProxyHelper.fromXml(xml, new DefaultTopicServiceProxyDependencies())); + } + + /** + * Getter for property NamedTopicSet.

+ */ + public Set getNamedTopicSet() + { + return __m_NamedTopicSet; + } + + /** + * Setter for property NamedTopicSet.

+ */ + public void setNamedTopicSet(Set setTopic) + { + __m_NamedTopicSet = setTopic; + } + + /** + * Return the Serializer associated with the given NamedTopic or null if the + * NamedTopic is an in-process cache. + */ + public static Serializer getSerializer(NamedTopic topic) + { + Serializer serializer = null; + if (topic instanceof SafeNamedTopic) + { + TopicService service = topic.getTopicService(); + serializer = service.getSerializer(); + } + return serializer; + } + + /** + * The cache factory that created this service. + */ + public ConfigurableCacheFactory getCacheFactory() + { + return __m_CacheFactory; + } + + /** + * Set the {@link ConfigurableCacheFactory}. + * + * @param ccf the {@link ConfigurableCacheFactory} + */ + public void setCacheFactory(ConfigurableCacheFactory ccf) + { + __m_CacheFactory = ccf; + } + + /** + * Getter for property TopicService.

+ */ + public TopicService getTopicService() + { + TopicService service = __m_TopicService; + return service == null ? this : service; + } + + /** + * Setter for property TopicService.

+ */ + public void setTopicService(TopicService sProperty) + { + __m_TopicService = sProperty; + } + + @Override + public void setSerializer(com.tangosol.io.Serializer serializer) + { + super.setSerializer(serializer); + + getConverterFromBinary().setSerializer(serializer); + getConverterToBinary().setSerializer(serializer); + } + + @Override + public void registerChannel(Channel channel) + { + super.registerChannel(channel); + setChannel(channel); + } + + @Override + public void unregisterChannel(Channel channel) + { + super.unregisterChannel(channel); + setChannel(null); + } + + public Channel getChannel() + { + return m_channel; + } + + public void setChannel(Channel channel) + { + m_channel = channel; + } + + /** + * Getter for property TransferThreshold.

+ */ + public long getTransferThreshold() + { + return __m_TransferThreshold; + } + + /** + * Setter for property TransferThreshold.

+ */ + public void setTransferThreshold(long lThreshold) + { + __m_TransferThreshold = lThreshold; + } + + /** + * True iff binary pass-through optimizations are enabled. + */ + public void setPassThroughEnabled(boolean fEnabled) + { + __m_PassThroughEnabled = fEnabled; + } + + /** + * True iff binary pass-through optimizations are enabled. + */ + public boolean isPassThroughEnabled() + { + return __m_PassThroughEnabled; + } + + /** + * Child ConverterFromBinary instance. + */ + public ConverterFromBinary getConverterFromBinary() + { + return __m_ConverterFromBinary; + } + + /** + * Child ConverterFromBinary instance. + */ + protected void setConverterFromBinary(ConverterFromBinary conv) + { + __m_ConverterFromBinary = conv; + } + + /** + * Child ConverterToBinary instance. + */ + public ConverterToBinary getConverterToBinary() + { + return __m_ConverterToBinary; + } + + /** + * Child ConverterToBinary instance. + */ + protected void setConverterToBinary(ConverterToBinary conv) + { + __m_ConverterToBinary = conv; + } + + // ----- inner class: ConverterFromBinary ------------------------------- + + /** + * Converter implementation that converts Objects from a Binary + * representation via the CacheServiceProxy's Serializer. + */ + public static class ConverterFromBinary + extends Converter + { + public ConverterFromBinary() + { + this(null, null, true); + } + + public ConverterFromBinary(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + @Override + public Object convert(Object o) + { + return o == null ? null : + ExternalizableHelper.fromBinary((Binary) o, getSerializer()); + } + } + + // ----- inner class: ConverterToBinary --------------------------------- + + /** + * Converter implementation that converts Objects to a Binary + * representation via the CacheServiceProxy's Serializer. + */ + public static class ConverterToBinary + extends Converter + { + public ConverterToBinary() + { + this(null, null, true); + } + + public ConverterToBinary(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + // Declared at the super level + public Object convert(Object o) + { + return ExternalizableHelper.toBinary(o, getSerializer()); + } + } + + // ----- inner class: ConverterTopic ------------------------------------ + + @SuppressWarnings("unchecked") + protected static class ConverterTopic + extends ConverterNamedTopic + implements PublisherConnector.Factory + { + public ConverterTopic(NamedTopic topic, ConverterToBinary convUp, ConverterFromBinary convDown, Serializer serializerDown) + { + super(topic, convUp, bin -> ExternalizableHelper.toBinary(convDown.convert(bin), serializerDown), + convDown, bin -> (Binary) convUp.convert(ExternalizableHelper.fromBinary(bin, serializerDown))); + } + + @Override + public Publisher createPublisher(Publisher.Option... options) + { + NamedTopicPublisher publisher = (NamedTopicPublisher) f_topic.createPublisher(options); + return new ConverterPublisher<>(publisher, this); + } + + @Override + public PublisherConnector createPublisherConnector(Publisher.Option[] options) + { + PublisherConnector connector = ((PublisherConnector.Factory) f_topic).createPublisherConnector(options); + return new ConverterPublisherConnector<>(connector, f_convBinaryUp, f_convBinaryDown); + } + + @Override + public Subscriber createSubscriber(Subscriber.Option... options) + { + NamedTopicSubscriber subscriber = (NamedTopicSubscriber) f_topic.createSubscriber(options); + return (Subscriber) new ConverterConnectedSubscriber<>(subscriber, this); + } + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/remoteService/RemoteTopicService.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/remoteService/RemoteTopicService.java new file mode 100644 index 0000000000000..27bba1dfea34d --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/net/extend/remoteService/RemoteTopicService.java @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: Component.net.extend.remoteService.RemoteTopicService + +package com.tangosol.coherence.component.net.extend.remoteService; + +import com.tangosol.coherence.Component; +import com.tangosol.coherence.component.net.extend.Protocol; +import com.tangosol.coherence.component.net.extend.RemoteNamedTopic; +import com.tangosol.coherence.component.net.extend.RemotePublisher; +import com.tangosol.coherence.component.net.extend.RemoteService; +import com.tangosol.coherence.component.net.extend.RemoteSubscriber; +import com.tangosol.coherence.component.net.extend.RemoteSubscriberChannel; +import com.tangosol.coherence.component.net.extend.messageFactory.TopicServiceFactory; +import com.tangosol.coherence.component.net.extend.messageFactory.TopicServiceFactory.EnsurePublisherRequest; +import com.tangosol.coherence.component.net.extend.messageFactory.TopicServiceFactory.EnsureSubscriberRequest; +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; +import com.tangosol.coherence.component.net.extend.protocol.TopicServiceProtocol; +import com.tangosol.internal.net.service.extend.remote.DefaultRemoteServiceDependencies; +import com.tangosol.internal.net.service.extend.remote.DefaultRemoteTopicServiceDependencies; +import com.tangosol.internal.net.service.extend.remote.LegacyXmlRemoteTopicServiceHelper; +import com.tangosol.internal.net.service.extend.remote.RemoteServiceDependencies; +import com.tangosol.internal.net.service.extend.remote.RemoteTopicServiceDependencies; +import com.tangosol.internal.net.topic.NamedTopicSubscriber; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberId; +import com.tangosol.net.RequestTimeoutException; +import com.tangosol.net.TopicService; +import com.tangosol.net.events.EventDispatcherRegistry; +import com.tangosol.net.events.internal.TopicDispatcher; +import com.tangosol.net.internal.ScopedTopicReferenceStore; +import com.tangosol.net.messaging.Channel; +import com.tangosol.net.messaging.Connection; +import com.tangosol.net.messaging.ConnectionEvent; +import com.tangosol.net.messaging.ConnectionInitiator; +import com.tangosol.net.security.SecurityHelper; +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.net.topic.TopicBackingMapManager; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Listeners; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.SimpleResourceRegistry; +import com.tangosol.util.WrapperException; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.security.auth.Subject; + +/** + * Service implementation that allows a JVM to use a remote clustered Service + * without having to join the Cluster. + * + * @see com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.ProxyService + */ +@SuppressWarnings({"rawtypes", "unchecked", "PatternVariableCanBeUsed", "deprecation"}) +public class RemoteTopicService + extends RemoteService + implements TopicService + { + /** + * Property ScopedTopicStore + */ + private ScopedTopicReferenceStore __m_ScopedTopicStore; + + private TopicBackingMapManager m_topicBackingMapManager; + + public RemoteTopicService() + { + this(null, null, true); + } + + public RemoteTopicService(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setMemberListeners(new Listeners()); + setResourceRegistry(new SimpleResourceRegistry()); + setScopedTopicStore(new ScopedTopicReferenceStore()); + setServiceListeners(new Listeners()); + setServiceVersion("1"); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new WrapperException(e); + } + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + //++ getter for static property _Instance + + /** + * Getter for property _Instance.

+ * Auto generated + */ + public static Component get_Instance() + { + return new RemoteTopicService(); + } + + /** + * Getter for property _CLASS.

+ * Property with auto-generated accessor that returns the Class object for a + * given component. + */ + public static Class get_CLASS() + { + return RemoteTopicService.class; + } + + /** + * Create a new Default dependencies object by cloning the input + * dependencies. Each class or component that uses dependencies implements + * a Default dependencies class which provides the clone functionality. + * The dependency injection design pattern requires every component in the + * component hierarchy to implement clone. + * + * @return DefaultRemoteServiceDependencies the cloned dependencies + */ + @Override + protected DefaultRemoteServiceDependencies cloneDependencies(RemoteServiceDependencies deps) + { + return new DefaultRemoteTopicServiceDependencies((RemoteTopicServiceDependencies) deps); + } + + @Override + public void connectionClosed(ConnectionEvent evt) + { + releaseTopics(); + super.connectionClosed(evt); + } + + @Override + public void connectionError(ConnectionEvent evt) + { + releaseTopics(); + super.connectionError(evt); + } + + + @Override + public void destroyTopic(NamedTopic topic) + { + if (!(topic instanceof RemoteNamedTopic)) + { + throw new IllegalArgumentException("illegal topic: " + topic); + } + RemoteNamedTopic remoteTopic = (RemoteNamedTopic) topic; + getScopedTopicStore().releaseTopic(remoteTopic); + destroyRemoteNamedTopic(remoteTopic); + } + + // Declared at the super level + + /** + * The configure() implementation method. This method must only be called by + * a thread that has synchronized on this RemoteService. + * + * @param xml the XmlElement containing the new configuration for this + * RemoteService + */ + @Override + protected void doConfigure(XmlElement xml) + { + setDependencies(LegacyXmlRemoteTopicServiceHelper.fromXml(xml, + new DefaultRemoteTopicServiceDependencies(), getOperationalContext(), + getContextClassLoader())); + } + + // Declared at the super level + + /** + * The shutdown() implementation method. This method must only be called by + * a thread that has synchronized on this RemoteService. + */ + @Override + protected void doShutdown() + { + super.doShutdown(); + + getScopedTopicStore().clear(); + } + + @Override + public int ensureChannelCount(String sName, int cChannel) + { + return ensureChannelCount(sName, cChannel, cChannel); + } + + @Override + public int ensureChannelCount(String sTopic, int cRequired, int cChannel) + { + Channel channel = ensureChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + TopicServiceFactory.EnsureChannelCountRequest request = (TopicServiceFactory.EnsureChannelCountRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_ENSURE_CHANNEL_COUNT); + + request.setTopicName(sTopic); + request.setRequiredChannels(cRequired); + request.setChannelCount(cChannel); + + return (Integer) channel.request(request); + } + + @Override + public NamedTopic ensureTopic(String sName, ClassLoader loader) + { + if (sName == null || sName.isEmpty()) + { + sName = "Default"; + } + + if (loader == null) + { + loader = getContextClassLoader(); + _assert(loader != null, "ContextClassLoader is missing"); + } + + ScopedTopicReferenceStore store = getScopedTopicStore(); + RemoteNamedTopic topic = (RemoteNamedTopic) store.getTopic(sName, loader); + + if (topic == null || !topic.isActive()) + { + // this is one of the few places that acquiring a distinct lock per topic + // is beneficial due to the potential cost of createRemoteNamedTopic + long cWait = getDependencies().getRequestTimeoutMillis(); + if (cWait <= 0) + { + cWait = -1; + } + if (!store.lock(sName, cWait)) + { + throw new RequestTimeoutException("Failed to get a reference to topic '" + + sName + "' after " + cWait + "ms"); + } + try + { + topic = (RemoteNamedTopic) store.getTopic(sName, loader); + if (topic == null || !topic.isActive()) + { + topic = createRemoteNamedTopic(sName, loader); + store.putTopic(topic, loader); + } + } + finally + { + store.unlock(sName); + } + } + return topic; + } + + @Override + public int getChannelCount(String sTopic) + { + Channel channel = ensureChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + TopicServiceFactory.ChannelCountRequest request = (TopicServiceFactory.ChannelCountRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_CHANNEL_COUNT); + + request.setTopicName(sTopic); + + return (Integer) channel.request(request); + } + + /** + * Getter for property ScopedTopicStore.

+ */ + public ScopedTopicReferenceStore getScopedTopicStore() + { + return __m_ScopedTopicStore; + } + + @Override + public String getServiceType() + { + return TopicService.TYPE_REMOTE; + } + + @Override + public Set getSubscriberGroups(String sTopicName) + { + Channel channel = ensureChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + TopicServiceFactory.GetSubscriberGroupsRequest request = (TopicServiceFactory.GetSubscriberGroupsRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_GET_SUBSCRIBER_GROUPS); + + request.setTopicName(sTopicName); + return new HashSet((Collection) channel.request(request)); + } + + @Override + public TopicBackingMapManager getTopicBackingMapManager() + { + return m_topicBackingMapManager; + } + + @Override + public Set getTopicNames() + { + return Collections.emptySet(); + } + + @Override + protected void onDependencies(RemoteServiceDependencies deps) + { + super.onDependencies(deps); + // register all Protocols + ConnectionInitiator initiator = getInitiator(); + initiator.registerProtocol(TopicServiceProtocol.getInstance()); + initiator.registerProtocol(NamedTopicProtocol.getInstance()); + } + + /** + * Open a Channel to the remote ProxyService. + */ + @Override + protected Channel openChannel() + { + lookupProxyServiceAddress(); + + Connection connection = getInitiator().ensureConnection(); + return connection.openChannel(TopicServiceProtocol.getInstance(), + "TopicServiceProxy", + null, + null, + SecurityHelper.getCurrentSubject()); + } + + @Override + public void releaseTopic(NamedTopic topic) + { + if (!(topic instanceof RemoteNamedTopic)) + { + throw new IllegalArgumentException("illegal topic: " + topic); + } + RemoteNamedTopic remoteTopic = (RemoteNamedTopic) topic; + getScopedTopicStore().releaseTopic(remoteTopic); + releaseRemoteNamedTopic(remoteTopic); + } + + @Override + public void setTopicBackingMapManager(TopicBackingMapManager topicBackingMapManager) + { + m_topicBackingMapManager = topicBackingMapManager; + } + + protected RemoteNamedTopic createRemoteNamedTopic(String sName, ClassLoader loader) + { + Channel channel = ensureChannel(); + Connection connection = channel.getConnection(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + RemoteNamedTopic topic = new RemoteNamedTopic<>(); + Subject subject = SecurityHelper.getCurrentSubject(); + TopicServiceFactory.EnsureTopicRequest request = (TopicServiceFactory.EnsureTopicRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_ENSURE_TOPIC); + + request.setTopicName(sName); + + URI uri; + try + { + uri = new URI((String) channel.request(request)); + } + catch (URISyntaxException e) + { + throw ensureRuntimeException(e, "error instantiating URI"); + } + + topic.setTopicName(sName); + topic.setTopicService(this); + topic.setEventDispatcher(ensureEventDispatcher()); + + TopicDispatcher dispatcher = new TopicDispatcher(sName, this); + topic.setTopicLifecycleEventDispatcher(dispatcher); + + ResourceRegistry resourceRegistry = getTopicBackingMapManager().getCacheFactory().getResourceRegistry(); + EventDispatcherRegistry dispatcherRegistry = resourceRegistry.getResource(EventDispatcherRegistry.class); + dispatcherRegistry.registerEventDispatcher(dispatcher); + + connection.acceptChannel(uri, loader, topic, subject); + + dispatcher.dispatchTopicCreated(topic); + return topic; + } + + protected void destroyRemoteNamedTopic(RemoteNamedTopic topic) + { + releaseRemoteNamedTopic(topic); + + Channel channel = ensureChannel(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + TopicServiceFactory.DestroyTopicRequest request = (TopicServiceFactory.DestroyTopicRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_DESTROY_TOPIC); + + request.setTopicName(topic.getTopicName()); + channel.request(request); + + TopicDispatcher dispatcher = topic.getTopicLifecycleEventDispatcher(); + dispatcher.dispatchTopicDestroyed(topic); + + ResourceRegistry resourceRegistry = getTopicBackingMapManager().getCacheFactory().getResourceRegistry(); + EventDispatcherRegistry dispatcherRegistry = resourceRegistry.getResource(EventDispatcherRegistry.class); + dispatcherRegistry.unregisterEventDispatcher(dispatcher); + } + + protected void releaseRemoteNamedTopic(RemoteNamedTopic topic) + { + try + { + // when this is called due to certain connection error, e.g. ping + // timeout, the channel could be null and closed. + Channel channel = topic.getChannel(); + if (channel != null) + { + channel.close(); + } + } + catch (RuntimeException e) + { + // ignored + } + } + + protected void releaseTopics() + { + ScopedTopicReferenceStore store = getScopedTopicStore(); + for (Object o : store.getAllTopics()) + { + RemoteNamedTopic topic = (RemoteNamedTopic) o; + releaseRemoteNamedTopic(topic); + } + + store.clear(); + } + + public RemotePublisher createPublisherConnector(String sTopicName, Publisher.Option[] options) + { + Channel channel = ensureChannel(); + Connection connection = channel.getConnection(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + Subject subject = SecurityHelper.getCurrentSubject(); + Publisher.OptionSet optionSet = Publisher.optionsFrom(options); + EnsurePublisherRequest request = (EnsurePublisherRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_ENSURE_PUBLISHER); + + request.setTopicName(sTopicName); + request.setChannelCount(optionSet.getChannelCount(0)); + + Object[] aoResponse = (Object[]) channel.request(request); + + _assert(aoResponse != null); + //noinspection DataFlowIssue + _assert(aoResponse.length >= 3); + + URI uri; + try + { + uri = new URI(String.valueOf(aoResponse[EnsurePublisherRequest.RESPONSE_ID_URI])); + } + catch (URISyntaxException e) + { + throw ensureRuntimeException(e, "error instantiating URI"); + } + + long nId; + try + { + nId = ((Number) aoResponse[EnsurePublisherRequest.RESPONSE_ID_PUBLISHER_ID]).longValue(); + } + catch (Exception e) + { + throw ensureRuntimeException(e, "error parsing publisher id"); + } + + long nMaxBatch; + try + { + nMaxBatch = ((Number) aoResponse[2]).longValue(); + } + catch (Exception e) + { + throw ensureRuntimeException(e, "error parsing max batch size"); + } + + int cChannel; + try + { + cChannel = ((Number) aoResponse[EnsurePublisherRequest.RESPONSE_ID_CHANNEL_COUNT]).intValue(); + } + catch (Exception e) + { + cChannel = getChannelCount(sTopicName); + } + + RemotePublisher connector = new RemotePublisher<>(nId, cChannel, options); + connector.setTopicName(sTopicName); + connector.setTopicService(this); + connector.setMaxBatchSizeBytes(nMaxBatch); + + connection.acceptChannel(uri, getContextClassLoader(), connector, subject); + return connector; + } + + public RemoteSubscriber createSubscriberConnector(String sTopicName, Subscriber.Option[] options) + { + Channel channel = ensureChannel(); + Connection connection = channel.getConnection(); + Protocol.MessageFactory factory = channel.getMessageFactory(); + Subject subject = SecurityHelper.getCurrentSubject(); + EnsureSubscriberRequest request = (EnsureSubscriberRequest) factory.createMessage(TopicServiceFactory.TYPE_ID_ENSURE_SUBSCRIBER); + + NamedTopicSubscriber.OptionSet optionSet = NamedTopicSubscriber.optionsFrom(options); + + optionSet.getSubscriberGroupName().ifPresent(request::setSubscriberGroup); + optionSet.getFilter().ifPresent(request::setFilter); + optionSet.getExtractor().ifPresent(request::setExtractor); + + request.setTopicName(sTopicName); + request.setCompleteOnEmpty(optionSet.isCompleteOnEmpty()); + + Object[] aoResponse = (Object[]) channel.request(request); + + _assert(aoResponse != null); + //noinspection DataFlowIssue + _assert(aoResponse.length >= 3); + + URI uri; + try + { + uri = new URI(String.valueOf(aoResponse[0])); + } + catch (URISyntaxException e) + { + throw ensureRuntimeException(e, "error instantiating URI"); + } + + SubscriberId subscriberId; + try + { + subscriberId = (SubscriberId) aoResponse[1]; + } + catch (Exception e) + { + throw ensureRuntimeException(e, "error parsing subscriber id"); + } + + SubscriberGroupId groupId; + try + { + groupId = (SubscriberGroupId) aoResponse[2]; + } + catch (Exception e) + { + throw ensureRuntimeException(e, "error parsing subscriber group id"); + } + + RemoteSubscriberChannel subscriberChannel = new RemoteSubscriberChannel<>(); + subscriberChannel.setSubscriberId(subscriberId); + subscriberChannel.setTopicName(sTopicName); + subscriberChannel.setTopicService(this); + subscriberChannel.setChannel(channel); + + connection.acceptChannel(uri, getContextClassLoader(), subscriberChannel, subject); + + return new RemoteSubscriber<>(sTopicName, subscriberChannel, subscriberId, groupId); + } + + /** + * Setter for property ScopedTopicStore.

+ */ + public void setScopedTopicStore(ScopedTopicReferenceStore storeTopic) + { + __m_ScopedTopicStore = storeTopic; + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/SafeCluster.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/SafeCluster.java index 1dc220d4a4a96..e6a1473a19519 100644 --- a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/SafeCluster.java +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/SafeCluster.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -17,6 +17,7 @@ import com.tangosol.coherence.component.net.Security; import com.tangosol.coherence.component.net.extend.remoteService.RemoteCacheService; import com.tangosol.coherence.component.net.extend.remoteService.RemoteInvocationService; +import com.tangosol.coherence.component.net.extend.remoteService.RemoteTopicService; import com.tangosol.coherence.component.net.management.Gateway; import com.tangosol.coherence.component.util.safeService.SafeCacheService; import com.tangosol.coherence.component.util.safeService.SafeInvocationService; @@ -24,6 +25,7 @@ import com.tangosol.coherence.component.util.safeService.safeCacheService.SafeDistributedCacheService; import com.tangosol.coherence.component.util.safeService.safeCacheService.safeDistributedCacheService.SafePagedTopicService; import com.oracle.coherence.common.base.Timeout; +import com.tangosol.coherence.component.util.safeService.safeTopicService.SafeSimpleTopicService; import com.tangosol.coherence.config.Config; import com.tangosol.coherence.config.builder.ParameterizedBuilder; import com.tangosol.config.expression.SystemPropertyParameterResolver; @@ -41,6 +43,7 @@ import com.tangosol.net.ProxyService; import com.tangosol.net.RequestTimeoutException; import com.tangosol.net.Service; +import com.tangosol.net.TopicService; import com.tangosol.net.internal.ClusterJoinException; import com.tangosol.net.management.Registry; import com.tangosol.net.security.DoAsAction; @@ -466,12 +469,14 @@ public com.tangosol.net.Service ensureLocalService(String sName, String sType) { // import Component.Net.Extend.RemoteService.RemoteCacheService; // import Component.Net.Extend.RemoteService.RemoteInvocationService; + // import Component.Net.Extend.RemoteService.RemoteTopicService; // import Component.Util.LocalCache; // import com.tangosol.coherence.config.Config; // import com.tangosol.net.CacheService; // import com.tangosol.net.ClusterDependencies$ServiceProvider as com.tangosol.net.ClusterDependencies.ServiceProvider; // import com.tangosol.net.InvocationService; // import com.tangosol.net.Service; + // import com.tangosol.net.TopicService; // import com.tangosol.util.Base; // import java.lang.ref.WeakReference; // import java.util.Set; @@ -492,6 +497,13 @@ else if (sType.equals(CacheService.TYPE_REMOTE)) service.setCluster(this); serviceLocal = service; } + else if (sType.equals(TopicService.TYPE_REMOTE)) + { + RemoteTopicService service = new RemoteTopicService(); + service.setServiceName(sName); + service.setCluster(this); + serviceLocal = service; + } else if (sType.equals(InvocationService.TYPE_REMOTE)) { RemoteInvocationService service = new RemoteInvocationService(); @@ -1137,21 +1149,23 @@ protected SafeService instantiateSafeService(com.tangosol.net.Service service) // import Component.Util.SafeService.SafeCacheService.SafeDistributedCacheService.SafePagedTopicService; // import Component.Util.SafeService.SafeInvocationService; // import Component.Util.SafeService.SafeProxyService; + // import Component.Util.SafeService.SafeTopicService; // import com.tangosol.net.CacheService; // import com.tangosol.net.DistributedCacheService; // import com.tangosol.net.InvocationService; // import com.tangosol.net.PagedTopicService; // import com.tangosol.net.ProxyService; - + // import com.tangosol.net.TopicService; + SafeService serviceSafe = service instanceof CacheService ? ( - service instanceof PagedTopicService ? new SafePagedTopicService() - - : service instanceof DistributedCacheService ? new SafeDistributedCacheService() - : new SafeCacheService() + service instanceof PagedTopicService ? new SafePagedTopicService() + : service instanceof DistributedCacheService ? new SafeDistributedCacheService() + : new SafeCacheService() ) : service instanceof InvocationService ? new SafeInvocationService() : service instanceof ProxyService ? new SafeProxyService() + : service instanceof TopicService ? new SafeSimpleTopicService() : new SafeService(); serviceSafe.setInternalService(service); @@ -1183,10 +1197,13 @@ public static boolean isLocalService(String sType) { // import com.tangosol.net.CacheService; // import com.tangosol.net.InvocationService; - + // import com.tangosol.net.TopicService; + return sType.equals(CacheService.TYPE_LOCAL) || sType.equals(CacheService.TYPE_REMOTE) || sType.equals(CacheService.TYPE_REMOTE_GRPC) || + sType.equals(TopicService.TYPE_REMOTE) || + sType.equals(TopicService.TYPE_REMOTE_GRPC) || sType.equals(InvocationService.TYPE_REMOTE); } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/Peer.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/Peer.java index c73c97038477d..2da84d76cdd13 100644 --- a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/Peer.java +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/Peer.java @@ -1780,7 +1780,7 @@ public void onInit() registerProtocol(protocol); // initialize the internal Connection and Channel - Connection connection = new Connection(); + Connection connection = createConnection(); connection.setConnectionManager(this); connection.setId(getProcessId()); connection.setMessageFactoryMap(Collections.singletonMap(protocol.getName(), @@ -1794,7 +1794,17 @@ public void onInit() super.onInit(); } - + + /** + * Create a new instance of a {@link Connection}. + * + * @return a new instance of a {@link Connection} + */ + protected Connection createConnection() + { + return new Connection(); + } + // From interface: com.tangosol.net.messaging.Channel$Receiver /** * Called on the service thread. diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/grid/ProxyService.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/grid/ProxyService.java index 76233fee9c24e..cc7a497d04c7d 100644 --- a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/grid/ProxyService.java +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/daemon/queueProcessor/service/grid/ProxyService.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -15,8 +15,11 @@ import com.tangosol.coherence.component.net.extend.protocol.CacheServiceProtocol; import com.tangosol.coherence.component.net.extend.protocol.InvocationServiceProtocol; import com.tangosol.coherence.component.net.extend.protocol.NamedCacheProtocol; +import com.tangosol.coherence.component.net.extend.protocol.NamedTopicProtocol; +import com.tangosol.coherence.component.net.extend.protocol.TopicServiceProtocol; import com.tangosol.coherence.component.net.extend.proxy.serviceProxy.CacheServiceProxy; import com.tangosol.coherence.component.net.extend.proxy.serviceProxy.InvocationServiceProxy; +import com.tangosol.coherence.component.net.extend.proxy.serviceProxy.TopicServiceProxy; import com.tangosol.coherence.component.net.memberSet.actualMemberSet.ServiceMemberSet; import com.tangosol.coherence.component.util.daemon.queueProcessor.service.peer.Acceptor; import com.tangosol.coherence.component.util.daemon.queueProcessor.service.peer.acceptor.GrpcAcceptor; @@ -39,6 +42,7 @@ import com.tangosol.net.OperationalContext; import com.tangosol.net.RequestPolicyException; import com.tangosol.net.Session; +import com.tangosol.net.TopicService; import com.tangosol.net.management.Registry; import com.tangosol.net.messaging.Connection; import com.tangosol.net.messaging.ConnectionAcceptor; @@ -46,6 +50,7 @@ import com.tangosol.net.messaging.ConnectionException; import com.tangosol.net.proxy.ProxyServiceLoadBalancer; import com.tangosol.net.proxy.RemoteMember; +import com.tangosol.net.topic.NamedTopic; import com.tangosol.run.xml.XmlElement; import com.tangosol.util.Base; import com.tangosol.util.LiteMap; @@ -119,7 +124,15 @@ public class ProxyService * Adapter. */ private com.tangosol.coherence.component.net.extend.proxy.serviceProxy.CacheServiceProxy __m_CacheServiceProxy; - + + /** + * Property TopicServiceProxy + * + * The cluster side portion (Proxy) of the client-to-cluster CacheService + * Adapter. + */ + private TopicServiceProxy __m_TopicServiceProxy; + /** * Property InvocationServiceProxy * @@ -618,6 +631,16 @@ public com.tangosol.coherence.component.net.extend.proxy.serviceProxy.CacheServi return __m_CacheServiceProxy; } + /** + * Getter for property TopicServiceProxy.

+ * The cluster side portion (Proxy) of the client-to-cluster TopicServiceProxy + * Adapter. + */ + public TopicServiceProxy getTopicServiceProxy() + { + return __m_TopicServiceProxy; + } + // From interface: com.tangosol.net.Session public com.tangosol.net.events.InterceptorRegistry getInterceptorRegistry() { @@ -1206,10 +1229,18 @@ public long getServiceLoadTimeMillis() return __m_ServiceLoadTimeMillis; } - // From interface: com.tangosol.net.Session - public com.tangosol.net.topic.NamedTopic getTopic(String Param_1, com.tangosol.net.NamedCollection.Option[] Param_2) + @Override + public NamedTopic getTopic(String sName, NamedTopic.Option... options) { - return null; + TopicService service = getTopicServiceProxy().getTopicService(); + if (service instanceof Session) + { + return ((Session) service).getTopic(sName, options); + } + else + { + return service.ensureTopic(sName, Base.ensureClassLoader(null)); + } } @Override @@ -1227,6 +1258,18 @@ public void close(NamedCollection col) service.releaseCache((NamedCache) col); } } + else if (col instanceof NamedTopic) + { + TopicService service = getTopicServiceProxy().getTopicService(); + if (service instanceof Session) + { + ((Session) service).close(col); + } + else + { + service.releaseTopic((NamedTopic) col); + } + } } @Override @@ -1244,6 +1287,18 @@ public void destroy(NamedCollection col) service.destroyCache((NamedCache) col); } } + else if (col instanceof NamedTopic) + { + TopicService service = getTopicServiceProxy().getTopicService(); + if (service instanceof Session) + { + ((Session) service).destroy(col); + } + else + { + service.destroyTopic((NamedTopic) col); + } + } } // Declared at the super level @@ -1392,14 +1447,22 @@ protected void onDependencies(com.tangosol.net.ServiceDependencies deps) { throw new IllegalStateException("missing required OperationalContext"); } - + + ConfigurableCacheFactory ccf = getResourceRegistry().getResource(ConfigurableCacheFactory.class, "ConfigurableCacheFactory"); + // create, set and configure the proxies CacheServiceProxy cacheServiceProxy = new CacheServiceProxy(); setCacheServiceProxy(cacheServiceProxy); cacheServiceProxy.setDependencies(proxyDeps.getCacheServiceProxyDependencies()); cacheServiceProxy.setContextClassLoader(getContextClassLoader()); - cacheServiceProxy.setCacheFactory((ConfigurableCacheFactory) getResourceRegistry().getResource(ConfigurableCacheFactory.class, "ConfigurableCacheFactory")); + cacheServiceProxy.setCacheFactory(ccf); + TopicServiceProxy topicServiceProxy = new TopicServiceProxy(); + setTopicServiceProxy(topicServiceProxy); + topicServiceProxy.setDependencies(proxyDeps.getTopicServiceProxyDependencies()); + topicServiceProxy.setContextClassLoader(getContextClassLoader()); + topicServiceProxy.setCacheFactory(ccf); + InvocationServiceProxy invocationServiceProxy = new InvocationServiceProxy(); setInvocationServiceProxy(invocationServiceProxy); invocationServiceProxy.setDependencies(proxyDeps @@ -1435,6 +1498,7 @@ protected void onDependencies(com.tangosol.net.ServiceDependencies deps) pool.setTaskTimeout(proxyDeps.getTaskTimeoutMillis()); pool.setThreadPriority(proxyDeps.getWorkerThreadPriority()); cacheServiceProxy.setDaemonPool(pool); + topicServiceProxy.setDaemonPool(pool); invocationServiceProxy.setDaemonPool(pool); } } @@ -1442,14 +1506,20 @@ protected void onDependencies(com.tangosol.net.ServiceDependencies deps) // register all Protocols with the Acceptor acceptor.registerProtocol(CacheServiceProtocol.getInstance()); + acceptor.registerProtocol(TopicServiceProtocol.getInstance()); acceptor.registerProtocol(InvocationServiceProtocol.getInstance()); acceptor.registerProtocol(NamedCacheProtocol.getInstance()); - + acceptor.registerProtocol(NamedTopicProtocol.getInstance()); + // register all Receivers with the Acceptor if (getCacheServiceProxy().isEnabled()) { acceptor.registerReceiver(getCacheServiceProxy()); } + if (getTopicServiceProxy().isEnabled()) + { + acceptor.registerReceiver(getTopicServiceProxy()); + } if (getInvocationServiceProxy().isEnabled()) { acceptor.registerReceiver(getInvocationServiceProxy()); @@ -1460,15 +1530,18 @@ protected void onDependencies(com.tangosol.net.ServiceDependencies deps) if (acceptor instanceof HttpAcceptor) { cacheServiceProxy.setPassThroughEnabled(false); + topicServiceProxy.setPassThroughEnabled(false); ((HttpAcceptor) acceptor).setSession(this); } else if (acceptor instanceof MemcachedAcceptor) { cacheServiceProxy.setPassThroughEnabled(false); + topicServiceProxy.setPassThroughEnabled(false); } else if (acceptor instanceof GrpcAcceptor) { cacheServiceProxy.setPassThroughEnabled(false); + topicServiceProxy.setPassThroughEnabled(false); } // Configure the load balancer @@ -1917,11 +1990,13 @@ protected void onServiceStarting() { Acceptor acceptorImpl = (Acceptor) acceptor; getCacheServiceProxy().setSerializer(acceptorImpl.ensureSerializer()); + getTopicServiceProxy().setSerializer(acceptorImpl.ensureSerializer()); getInvocationServiceProxy().setSerializer(acceptorImpl.ensureSerializer()); } else { getCacheServiceProxy().setSerializer(ensureSerializer()); + getTopicServiceProxy().setSerializer(ensureSerializer()); getInvocationServiceProxy().setSerializer(ensureSerializer()); } @@ -2192,7 +2267,18 @@ protected void setCacheServiceProxy(com.tangosol.coherence.component.net.extend. _assert(getCacheServiceProxy() == null); __m_CacheServiceProxy = (proxy); } - + + /** + * Setter for property TopicServiceProxy.

+ * The cluster side portion (Proxy) of the client-to-cluster TopicServiceProxy + * Adapter. + */ + protected void setTopicServiceProxy(TopicServiceProxy proxy) + { + _assert(__m_TopicServiceProxy == null); + __m_TopicServiceProxy = proxy; + } + // From interface: com.tangosol.net.ProxyService // Declared at the super level /** @@ -2212,10 +2298,16 @@ public void setContextClassLoader(ClassLoader loader) acceptor.setContextClassLoader(loader); } - CacheServiceProxy proxy = getCacheServiceProxy(); - if (proxy != null) + CacheServiceProxy cacheProxy = getCacheServiceProxy(); + if (cacheProxy != null) + { + cacheProxy.setContextClassLoader(loader); + } + + TopicServiceProxy topicProxy = getTopicServiceProxy(); + if (topicProxy != null) { - proxy.setContextClassLoader(loader); + topicProxy.setContextClassLoader(loader); } } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleNamedTopic.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleNamedTopic.java new file mode 100644 index 0000000000000..1cccea2fa658f --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleNamedTopic.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.util.safeService.safeTopicService; + +import com.tangosol.coherence.Component; +import com.tangosol.coherence.component.util.SafeNamedTopic; +import com.tangosol.internal.net.topic.NamedTopicSubscriber; +import com.tangosol.internal.net.topic.NamedTopicPublisher; +import com.tangosol.internal.net.topic.PublisherConnector; +import com.tangosol.internal.net.topic.SubscriberConnector; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; + +/** + * A safe wrapper around a {@link com.tangosol.net.topic.NamedTopic}. + * + * @param the type of element in the topic + * + * @author Jonathan Knight 2024.11.26 + */ +public class SafeSimpleNamedTopic + extends SafeNamedTopic + { + public SafeSimpleNamedTopic() + { + this(null, null, true); + } + + public SafeSimpleNamedTopic(String sName, Component compParent, boolean fInit) + { + super(sName, compParent, fInit); + } + + @Override + @SuppressWarnings("unchecked") + public PublisherConnector createPublisherConnector(Publisher.Option[] options) + { + PublisherConnector.Factory factory = (PublisherConnector.Factory) getRunningNamedTopic(); + return factory.createPublisherConnector(options); + } + + @Override + @SuppressWarnings("unchecked") + public SubscriberConnector createSubscriberConnector(Subscriber.Option[] options) + { + SubscriberConnector.Factory factory = (SubscriberConnector.Factory) getRunningNamedTopic(); + return factory.createSubscriberConnector(options); + } + } diff --git a/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleTopicService.java b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleTopicService.java new file mode 100644 index 0000000000000..cd67539570e37 --- /dev/null +++ b/prj/coherence-core-components/src/main/java/com/tangosol/coherence/component/util/safeService/safeTopicService/SafeSimpleTopicService.java @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.component.util.safeService.safeTopicService; + +import com.tangosol.coherence.component.util.SafeNamedTopic; +import com.tangosol.coherence.component.util.safeService.SafeTopicService; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; +import com.tangosol.net.Service; +import com.tangosol.net.TopicService; +import com.tangosol.net.internal.ScopedTopicReferenceStore; +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.net.topic.TopicBackingMapManager; +import com.tangosol.util.ListMap; + +import java.security.PrivilegedAction; +import java.util.Set; + +/** + * A simple safe wrapper around a {@link TopicService}. + * + * @author Jonathan Knight 2024.11.26 + */ +@SuppressWarnings("rawtypes") +public class SafeSimpleTopicService + extends com.tangosol.coherence.component.util.SafeService + implements SafeTopicService + { + /** + * Property ScopedTopicStore + * + */ + private ScopedTopicReferenceStore __m_ScopedTopicStore; + + /** + * Property TopicBackingMapManager + * + */ + private transient TopicBackingMapManager __m_TopicBackingMapManager; + + private static ListMap> __mapChildren; + + static + { + __initStatic(); + } + + private static void __initStatic() + { + // register child classes + __mapChildren = new ListMap<>(); + __mapChildren.put("DestroyTopicAction", DestroyTopicAction.class); + __mapChildren.put("EnsureServiceAction", EnsureServiceAction.class); + __mapChildren.put("ReleaseTopicAction", ReleaseTopicAction.class); + __mapChildren.put("StartAction", StartAction.class); + __mapChildren.put("Unlockable", Unlockable.class); + } + + public SafeSimpleTopicService() + { + this(null, null, true); + } + + public SafeSimpleTopicService(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + + // state initialization: public and protected properties + try + { + setLock(new java.util.concurrent.locks.ReentrantLock()); + setResourceRegistry(new com.tangosol.util.SimpleResourceRegistry()); + setSafeServiceState(0); + setScopedTopicStore(new com.tangosol.net.internal.ScopedTopicReferenceStore()); + } + catch (Exception e) + { + // re-throw as a runtime exception + throw new com.tangosol.util.WrapperException(e); + } + + // containment initialization: children + + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + + super.__initPrivate(); + } + + /** + * This is an auto-generated method that returns the map of design time + * [static] children. + *

+ * Note: the class generator will ignore any custom implementation for this + * behavior. + */ + @Override + protected java.util.Map get_ChildClasses() + { + return __mapChildren; + } + + @Override + public int ensureChannelCount(String sName, int cChannel) + { + return getRunningTopicService().ensureChannelCount(sName, cChannel); + } + + @Override + public int ensureChannelCount(String Param_1, int Param_2, int Param_3) + { + return getRunningTopicService().ensureChannelCount(Param_1, Param_2, Param_3); + } + + @Override + public int getChannelCount(String Param_1) + { + return getRunningTopicService().getChannelCount(Param_1); + } + + @Override + protected void cleanup() + { + super.cleanup(); + setTopicBackingMapManager(null); + getScopedTopicStore().clear(); + } + + @Override + public SafeNamedTopic createSafeTopic(String sName) + { + return new SafeSimpleNamedTopic(); + } + + public Publisher createPublisher(String sTopicName) + { + return createPublisher(sTopicName, new com.tangosol.net.topic.Publisher.Option[0]); + } + + public Publisher createPublisher(String sTopicName, Publisher.Option[] options) + { + throw new UnsupportedOperationException("Implement this method!"); + } + + public Subscriber createSubscriber(String sTopicName) + { + return createSubscriber(sTopicName, new Subscriber.Option[0]); + } + + public com.tangosol.net.topic.Subscriber createSubscriber(String sTopicName, Subscriber.Option[] options) + { + throw new UnsupportedOperationException("Implement this method!"); + } + + @Override + public void checkClientThread(String sName) + { + super.checkClientThread(sName); + } + + @Override + public Service getInternalService() + { + return super.getInternalService(); + } + + @Override + public void destroyTopic(NamedTopic topic) + { + SafeNamedTopic topicSafe = (SafeNamedTopic) topic; + removeTopicReference(topicSafe); + + DestroyTopicAction action = new DestroyTopicAction(); + action.setSafeNamedTopic(topicSafe); + action.setTopicService((TopicService) getInternalService()); + action.run(); + } + + /** + * Getter for property RunningTopicService.

+ */ + @Override + public TopicService getRunningTopicService() + { + return (TopicService) getRunningService(); + } + + // Accessor for the property "ScopedTopicStore" + /** + * Getter for property ScopedTopicStore.

+ */ + public com.tangosol.net.internal.ScopedTopicReferenceStore getScopedTopicStore() + { + return __m_ScopedTopicStore; + } + + @Override + public Set getSubscriberGroups(String sTopicName) + { + return getRunningTopicService().getSubscriberGroups(sTopicName); + } + + /** + * Getter for property TopicBackingMapManager.

+ */ + @Override + public TopicBackingMapManager getTopicBackingMapManager() + { + return __m_TopicBackingMapManager; + } + + @Override + public Set getTopicNames() + { + return getRunningTopicService().getTopicNames(); + } + + @Override + public void releaseTopic(NamedTopic topic) + { + SafeNamedTopic topicSafe = (SafeNamedTopic) topic; + + removeTopicReference(topicSafe); + + ReleaseTopicAction action = (ReleaseTopicAction) _newChild("ReleaseTopicAction"); + action.setSafeNamedTopic(topicSafe); + action.setTopicService((TopicService) getInternalService()); + + action.run(); + } + + @Override + public void removeTopicReference(SafeNamedTopic topicSafe) + { + topicSafe.setReleased(true); + getScopedTopicStore().releaseTopic(topicSafe); + } + + /** + * Setter for property ScopedTopicStore.

+ */ + public void setScopedTopicStore(com.tangosol.net.internal.ScopedTopicReferenceStore storeTopic) + { + __m_ScopedTopicStore = storeTopic; + } + + /** + * Setter for property TopicBackingMapManager.

+ */ + @Override + public void setTopicBackingMapManager(com.tangosol.net.topic.TopicBackingMapManager managerMap) + { + __m_TopicBackingMapManager = managerMap; + getRunningTopicService().setTopicBackingMapManager(managerMap); + } + + @Override + protected void startService(com.tangosol.net.Service service) + { + ((TopicService) service).setTopicBackingMapManager(getTopicBackingMapManager()); + super.startService(service); + } + + // ----- inner class: DestroyTopicAction -------------------------------- + + public static class DestroyTopicAction + extends com.tangosol.coherence.component.Util + implements PrivilegedAction + { + /** + * Property SafeNamedTopic + * + */ + private SafeNamedTopic __m_SafeNamedTopic; + + /** + * Property TopicService + * + */ + private TopicService __m_TopicService; + + public DestroyTopicAction() + { + this(null, null, true); + } + + public DestroyTopicAction(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + @Override + public void __init() + { + // private initialization + __initPrivate(); + // signal the end of the initialization + set_Constructed(true); + } + + @Override + protected void __initPrivate() + { + super.__initPrivate(); + } + + /** + * Getter for property SafeNamedTopic.

+ */ + public SafeNamedTopic getSafeNamedTopic() + { + return __m_SafeNamedTopic; + } + + /** + * Getter for property TopicService.

+ */ + public TopicService getTopicService() + { + return __m_TopicService; + } + + @Override + public Object run() + { + TopicService serviceInternal = getTopicService(); + NamedTopic topicInternal = getSafeNamedTopic().getNamedTopic(); + + if (topicInternal == null) + { + throw new IllegalStateException("Topic is already released"); + } + + try + { + serviceInternal.destroyTopic(topicInternal); + } + catch (RuntimeException e) + { + if (serviceInternal != null && serviceInternal.isRunning()) + { + throw e; + } + } + + return null; + } + + /** + * Setter for property SafeNamedTopic.

+ */ + public void setSafeNamedTopic(SafeNamedTopic topicNamed) + { + __m_SafeNamedTopic = topicNamed; + } + + /** + * Setter for property TopicService.

+ */ + public void setTopicService(TopicService serviceTopic) + { + __m_TopicService = serviceTopic; + } + } + + // ----- inner class: ReleaseTopicAction -------------------------------- + + public static class ReleaseTopicAction + extends com.tangosol.coherence.component.Util + implements java.security.PrivilegedAction + { + // ---- Fields declarations ---- + + /** + * Property SafeNamedTopic + * + */ + private SafeNamedTopic __m_SafeNamedTopic; + + /** + * Property TopicService + * + */ + private TopicService __m_TopicService; + + // Default constructor + public ReleaseTopicAction() + { + this(null, null, true); + } + + // Initializing constructor + public ReleaseTopicAction(String sName, com.tangosol.coherence.Component compParent, boolean fInit) + { + super(sName, compParent, false); + + if (fInit) + { + __init(); + } + } + + // Main initializer + public void __init() + { + // private initialization + __initPrivate(); + + + // signal the end of the initialization + set_Constructed(true); + } + + // Private initializer + protected void __initPrivate() + { + + super.__initPrivate(); + } + + //++ getter for static property _Instance + /** + * Getter for property _Instance.

+ * Auto generated + */ + public static com.tangosol.coherence.Component get_Instance() + { + return new ReleaseTopicAction(); + } + + //++ getter for static property _CLASS + /** + * Getter for property _CLASS.

+ * Property with auto-generated accessor that returns the Class object + * for a given component. + */ + public static Class get_CLASS() + { + Class clz; + try + { + clz = Class.forName("com.tangosol.coherence/component/util/safeService/SafeTopicService$ReleaseTopicAction".replace('/', '.')); + } + catch (ClassNotFoundException e) + { + throw new NoClassDefFoundError(e.getMessage()); + } + return clz; + } + + //++ getter for autogen property _Module + /** + * This is an auto-generated method that returns the global [design + * time] parent component. + *

+ * Note: the class generator will ignore any custom implementation for + * this behavior. + */ + private com.tangosol.coherence.Component get_Module() + { + return this.get_Parent(); + } + + // Accessor for the property "SafeNamedTopic" + /** + * Getter for property SafeNamedTopic.

+ */ + public SafeNamedTopic getSafeNamedTopic() + { + return __m_SafeNamedTopic; + } + + // Accessor for the property "TopicService" + /** + * Getter for property TopicService.

+ */ + public TopicService getTopicService() + { + return __m_TopicService; + } + + // From interface: java.security.PrivilegedAction + public Object run() + { + // import com.tangosol.net.TopicService; + // import com.tangosol.net.topic.NamedTopic; + + TopicService serviceInternal = getTopicService(); + NamedTopic topicInternal = getSafeNamedTopic().getNamedTopic(); + + if (topicInternal == null) + { + throw new IllegalStateException("Topic is already released"); + } + + try + { + serviceInternal.destroyTopic(topicInternal); + } + catch (RuntimeException e) + { + if (serviceInternal != null && serviceInternal.isRunning()) + { + throw e; + } + } + + return null; + } + + // Accessor for the property "SafeNamedTopic" + /** + * Setter for property SafeNamedTopic.

+ */ + public void setSafeNamedTopic(SafeNamedTopic topicNamed) + { + __m_SafeNamedTopic = topicNamed; + } + + // Accessor for the property "TopicService" + /** + * Setter for property TopicService.

+ */ + public void setTopicService(TopicService serviceTopic) + { + __m_TopicService = serviceTopic; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java index 3bac4703f3f00..8407e1b0a0465 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -62,7 +62,7 @@ public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependen if (!(service instanceof CacheService)) { throw new IllegalArgumentException("Error: ensureCache is using service " - + service.getInfo().getServiceName() + "that is not a CacheService "); + + service.getInfo().getServiceName() + " that is not a CacheService "); } NamedCache cache = ((CacheService) service).ensureCache(dependencies.getCacheName(), diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BaseGrpcTopicScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BaseGrpcTopicScheme.java new file mode 100644 index 0000000000000..267f9c7b9462f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BaseGrpcTopicScheme.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.util.Options; + +import com.tangosol.coherence.config.builder.NamedCollectionBuilder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.grpc.DefaultRemoteGrpcTopicServiceDependencies; + +import com.tangosol.internal.net.topic.DefaultTopicDependencies; + +import com.tangosol.net.Cluster; +import com.tangosol.net.ClusterDependencies; +import com.tangosol.net.NamedCollection; +import com.tangosol.net.Service; +import com.tangosol.net.TopicService; +import com.tangosol.net.ValueTypeAssertion; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.TopicDependencies; + +/** + * The {@link BaseGrpcTopicScheme} is responsible for building a + * remote gRPC topic service. + *

+ * This class is sub-classed in the Coherence Java gRPC client module + * and that subclass does all the actual work. This allows the grpc + * remote scheme to be added to a cache configuration file even if the + * gRPC client is not on the class path and nothing will break. + * + * @author Jonathan Knight 2025.01.01 + */ +public class BaseGrpcTopicScheme + extends BaseGrpcScheme + implements NamedTopicScheme + { + /** + * Constructs a {@link BaseGrpcTopicScheme}. + */ + public BaseGrpcTopicScheme() + { + super(new DefaultRemoteGrpcTopicServiceDependencies()); + } + + @Override + @SuppressWarnings("rawtypes") + public NamedTopic realize(ValueTypeAssertion assertion, ParameterResolver resolver, Dependencies deps) + { + validate(resolver); + + String sName = deps.getCacheName(); + TopicService service = ensureConfiguredService(resolver, deps); + + return service.ensureTopic(sName, deps.getClassLoader()); + } + + @Override + public boolean realizes(Class type) + { + return NamedTopic.class.equals(type); + } + + @Override + public TopicDependencies createConfiguration(ParameterResolver resolver, ClassLoader loader) + { + return new DefaultTopicDependencies(); + } + + @Override + @SuppressWarnings("rawtypes") + public NamedCollectionBuilder getNamedCollectionBuilder(Class clz, Options options) + { + // an exception will be thrown if the Coherence gRPC client is not on the class path, + // as the client overrides this method. + throw new UnsupportedOperationException("The Coherence gRPC client is not available"); + } + + // ----- ServiceScheme interface --------------------------------------- + + @Override + public String getServiceType() + { + return TopicService.TYPE_REMOTE_GRPC; + } + + @Override + @SuppressWarnings("unchecked") + protected Service ensureService(String sService, Cluster cluster) + { + ClusterDependencies.ServiceProvider provider = getServiceProvider(); + if (provider == null) + { + // an exception will be thrown if the Coherence gRPC client is not on the class path, + // as the client overrides this method. + throw new UnsupportedOperationException("The Coherence gRPC client is not available"); + } + cluster.getDependencies().addLocalServiceProvider(TopicService.TYPE_REMOTE_GRPC, provider); + return super.ensureService(sService, cluster); + } + + + // ----- ServiceBuilder interface --------------------------------------- + + @Override + @SuppressWarnings("unchecked") + public TopicService ensureConfiguredService(ParameterResolver resolver, Dependencies deps) + { + ClusterDependencies.ServiceProvider provider = getServiceProvider(); + return provider.ensureConfiguredService(resolver, deps); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteTopicScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteTopicScheme.java new file mode 100644 index 0000000000000..a22b9334fd913 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteTopicScheme.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.net.InetAddresses; +import com.oracle.coherence.common.util.Options; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.config.injection.SimpleInjector; +import com.tangosol.internal.net.service.extend.remote.DefaultRemoteTopicServiceDependencies; +import com.tangosol.internal.net.service.extend.remote.RemoteTopicServiceDependencies; + +import com.tangosol.internal.net.topic.DefaultTopicDependencies; +import com.tangosol.net.CacheFactory; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.net.NamedCollection; +import com.tangosol.net.TopicService; +import com.tangosol.net.Cluster; +import com.tangosol.net.Service; +import com.tangosol.net.ValueTypeAssertion; +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.TopicDependencies; +import com.tangosol.util.ResourceResolver; +import com.tangosol.util.ResourceResolverHelper; + +/** + * The {@link RemoteTopicScheme} is responsible for building a remote topic. + * + * @author Jonathan Knight 2025.01.01 + */ +public class RemoteTopicScheme + extends AbstractCachingScheme + implements NamedTopicScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link RemoteTopicScheme}. + */ + public RemoteTopicScheme() + { + m_serviceDependencies = new DefaultRemoteTopicServiceDependencies(); + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return TopicService.TYPE_REMOTE; + } + + // ----- ServiceBuilder interface --------------------------------------- + + @Override + public boolean isRunningClusterNeeded() + { + return false; + } + + @Override + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + Service service = super.realizeService(resolver, loader, cluster); + + injectScopeNameIntoService(service); + + return service; + } + + @Override + public RemoteTopicScheme getNamedCollectionBuilder(Class clz, Options options) + { + if (clz.isAssignableFrom(NamedTopic.class)) + { + return this; + } + return null; + } + + // ----- NamedTopicScheme methods --------------------------------------- + + @Override + public TopicDependencies createConfiguration(ParameterResolver resolver, ClassLoader loader) + { + SimpleInjector injector = new SimpleInjector(); + ResourceResolver resourceResolver = ResourceResolverHelper.resourceResolverFrom(RemoteTopicScheme.class, this); + + injector.inject(this, ResourceResolverHelper.resourceResolverFrom(resourceResolver, resourceResolver)); + + Cluster cluster = CacheFactory.getCluster(); + int nMTU = InetAddresses.getLocalMTU(cluster.getLocalMember().getAddress()); + + if (nMTU == 0) + { + nMTU = 1500; + } + + int nMaxBatchSizeBytes; + try + { + // Detect Overflow.InetAddresses.getLocalMTU(NetworkInterface) returns Integer.MAX_VALUE on Windows/jdk11 for loopback. + nMaxBatchSizeBytes = Math.multiplyExact(nMTU, cluster.getDependencies().getPublisherCloggedCount()); + } + catch (ArithmeticException e) + { + nMaxBatchSizeBytes = Integer.MAX_VALUE; + } + + DefaultTopicDependencies deps = new DefaultTopicDependencies(); + deps.setMaxBatchSizeBytes(nMaxBatchSizeBytes); + + return deps; + } + + @Override + public TopicService ensureConfiguredService(ParameterResolver resolver, Dependencies deps) + { + return getOrEnsureService(deps); + } + + @SuppressWarnings("rawtypes") + @Override + public NamedTopic realize(ValueTypeAssertion typeConstraint, + ParameterResolver resolver, Dependencies deps) + { + validate(resolver); + + String sName = deps.getCacheName(); + TopicService service = ensureConfiguredService(resolver, deps); + + return service.ensureTopic(sName, deps.getClassLoader()); + } + + @Override + public boolean realizes(Class type) + { + return NamedTopic.class.equals(type); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Get or ensure service corresponding to this scheme. + *

+ * Optimized to avoid ensureService synchronization on cluster and service + * when possible. This behavior is required on server side. Intermittent deadlock occurs + * calling ensureService on server side from inside service implementation. + * + * @return {@link TopicService} + */ + private TopicService getOrEnsureService(Dependencies deps) + { + // Call ECFF to ensure the Service. CCF must be used to ensure the service, rather + // than the service builder. This is because ECCF.ensureService provides additional + // logic like injecting a BackingMapManager into the service and starting the Service. + Service service = + ((ExtensibleConfigurableCacheFactory) deps.getConfigurableCacheFactory()).ensureService(this); + + if (!(service instanceof TopicService)) + { + throw new IllegalArgumentException("Error: ensureTopic is using service " + + service.getInfo().getServiceName() + "that is not a TopicService "); + } + + return (TopicService) service; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java index ce3b268828cce..24125ac6ffe59 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -8,9 +8,13 @@ import com.tangosol.coherence.config.builder.MapBuilder; import com.tangosol.coherence.config.builder.NamedCollectionBuilder; + import com.tangosol.config.expression.ParameterResolver; + import com.tangosol.net.NamedCollection; +import com.tangosol.net.topic.TopicDependencies; + /** * The {@link TopicScheme} class is responsible for building a fully * configured instance of a topic. @@ -30,4 +34,14 @@ public interface TopicScheme * @return a configured topic service */ public S ensureConfiguredService(ParameterResolver resolver, MapBuilder.Dependencies deps); + + /** + * Create a {@link TopicDependencies} based on the values contained in this scheme. + * + * @param resolver the {@link ParameterResolver} to use to resolve configuration values + * @param loader the {@link ClassLoader} to use + * + * @return a {@link TopicDependencies} based on the values contained in this scheme + */ + public TopicDependencies createConfiguration(ParameterResolver resolver, ClassLoader loader); } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java index ed378aca8b8a4..ef4b81eb2eb03 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -14,6 +14,7 @@ import com.tangosol.coherence.config.builder.storemanager.CustomStoreManagerBuilder; import com.tangosol.coherence.config.builder.storemanager.NioFileManagerBuilder; +import com.tangosol.coherence.config.scheme.BaseGrpcTopicScheme; import com.tangosol.coherence.config.scheme.CacheStoreScheme; import com.tangosol.coherence.config.scheme.CaffeineScheme; @@ -35,6 +36,7 @@ import com.tangosol.coherence.config.scheme.RemoteCacheScheme; import com.tangosol.coherence.config.scheme.BaseGrpcCacheScheme; import com.tangosol.coherence.config.scheme.RemoteInvocationScheme; +import com.tangosol.coherence.config.scheme.RemoteTopicScheme; import com.tangosol.coherence.config.scheme.ReplicatedScheme; import com.tangosol.coherence.config.scheme.TransactionalScheme; import com.tangosol.coherence.config.scheme.ViewScheme; @@ -139,6 +141,7 @@ import com.tangosol.net.OperationalContext; import com.tangosol.net.SocketOptions; +import com.tangosol.net.TopicService; import com.tangosol.net.messaging.Codec; import com.tangosol.net.partition.KeyAssociator; @@ -196,10 +199,13 @@ public CacheConfigNamespaceHandler() odp.addDefaultsDefinition("remote-cache-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_REMOTE)); odp.addDefaultsDefinition("remote-grpc-cache-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_REMOTE_GRPC)); + odp.addDefaultsDefinition("remote-grpc-topic-scheme", + CacheFactory.getServiceConfig(TopicService.TYPE_REMOTE_GRPC)); odp.addDefaultsDefinition("remote-invocation-scheme", CacheFactory.getServiceConfig(InvocationService.TYPE_REMOTE)); - odp.addDefaultsDefinition("paged-topic-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_DISTRIBUTED)); + odp.addDefaultsDefinition("paged-topic-scheme", CacheFactory.getServiceConfig(TopicService.TYPE_PAGED_TOPIC)); + odp.addDefaultsDefinition("remote-topic-scheme", CacheFactory.getServiceConfig(TopicService.TYPE_REMOTE)); dep.addElementPreprocessor(odp); @@ -350,6 +356,10 @@ public CacheConfigNamespaceHandler() new ServiceBuilderProcessor<>(BaseGrpcCacheScheme.class)); registerProcessor("remote-invocation-scheme", new ServiceBuilderProcessor<>(RemoteInvocationScheme.class)); + registerProcessor("remote-topic-scheme", + new ServiceBuilderProcessor<>(RemoteTopicScheme.class)); + registerProcessor("remote-grpc-topic-scheme", + new ServiceBuilderProcessor<>(BaseGrpcTopicScheme.class)); registerProcessor("replicated-scheme", new ServiceBuilderProcessor<>(ReplicatedScheme.class)); registerProcessor("request-timeout", new MillisProcessor()); registerProcessor("standard-lease-milliseconds", new MillisProcessor()); @@ -358,7 +368,7 @@ public CacheConfigNamespaceHandler() registerProcessor("task-timeout", new MillisProcessor()); registerProcessor("cache", new CacheMappingProcessor()); registerProcessor("topic-mapping", new TopicMappingProcessor("topic-name", NamedTopicScheme.class)); - registerProcessor("topic-scheme", new PagedTopicSchemeProcessor()); + registerProcessor("paged-topic-scheme", new PagedTopicSchemeProcessor()); registerProcessor("transactional-scheme", new ServiceBuilderProcessor<>(TransactionalScheme.class)); registerProcessor("transfer-threshold", new MemorySizeProcessor()); diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcServiceDependencies.java index c97cacf58fd10..9cfd263a1e79b 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcServiceDependencies.java +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcServiceDependencies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -277,6 +277,26 @@ public void setRequireHeartbeatAck(boolean fRequireHeartbeatAck) m_fRequireHeartbeatAck = fRequireHeartbeatAck; } + /** + * {@inheritDoc} + */ + @Override + public boolean isDeferKeyAssociationCheck() + { + return m_fDeferKeyAsssocationCheck; + } + + /** + * Set the flag to defer the KeyAssociation check. + * + * @param fDefer the KeyAssociation check defer flag + */ + @Injectable("defer-key-association-check") + public void setDeferKeyAssociationCheck(boolean fDefer) + { + m_fDeferKeyAsssocationCheck = fDefer; + } + // ----- helper methods ------------------------------------------------- protected DefaultDaemonPoolDependencies ensureDaemonPoolDependencies() @@ -331,4 +351,9 @@ protected DefaultDaemonPoolDependencies ensureDaemonPoolDependencies() * The flag to indicate whether heart beat messages require an ack from the server. */ private boolean m_fRequireHeartbeatAck = false; + + /** + * The flag to indicate if the KeyAssociation check is deferred. + */ + private boolean m_fDeferKeyAsssocationCheck; } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcTopicServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcTopicServiceDependencies.java new file mode 100644 index 0000000000000..5c9b2fdd49bf2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/DefaultRemoteGrpcTopicServiceDependencies.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.grpc; + +/** + * A default implementation of {@link RemoteGrpcTopicServiceDependencies}. + * + * @author Jonathan Knight 2025.01.01 + */ +public class DefaultRemoteGrpcTopicServiceDependencies + extends DefaultRemoteGrpcServiceDependencies + implements RemoteGrpcTopicServiceDependencies + { + /** + * Create a {@link DefaultRemoteGrpcTopicServiceDependencies}. + */ + public DefaultRemoteGrpcTopicServiceDependencies() + { + this(null); + } + + /** + * Create a {@link DefaultRemoteGrpcTopicServiceDependencies} by copying + * the specified {@link RemoteGrpcTopicServiceDependencies}. + * + * @param deps the {@link RemoteGrpcTopicServiceDependencies} to copy + */ + public DefaultRemoteGrpcTopicServiceDependencies(RemoteGrpcTopicServiceDependencies deps) + { + super(deps); + } + + @Override + public long getRequestTimeoutMillis() + { + long cMillis = super.getRequestTimeoutMillis(); + if (cMillis == 0) + { + cMillis = 30000; + } + return cMillis; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/RemoteGrpcTopicServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/RemoteGrpcTopicServiceDependencies.java new file mode 100644 index 0000000000000..0305eb668b8a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/grpc/RemoteGrpcTopicServiceDependencies.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.grpc; + +/** + * The RemoteGrpcTopicServiceDependencies interface provides a gRPC + * RemoteTopicService with its external dependencies. + * + * @author Jonathan Knight 2025.01.01 + */ +public interface RemoteGrpcTopicServiceDependencies + extends RemoteGrpcServiceDependencies + { + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/LegacyXmlServiceHelper.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/LegacyXmlServiceHelper.java index a8e2dc1436ada..305f2eb5964c1 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/LegacyXmlServiceHelper.java +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/LegacyXmlServiceHelper.java @@ -1,13 +1,11 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ package com.tangosol.internal.net.service; -import com.tangosol.coherence.config.scheme.PagedTopicScheme; - import com.tangosol.io.ConfigurableSerializerFactory; import com.tangosol.io.SerializerFactory; @@ -16,6 +14,8 @@ import com.tangosol.net.InvocationService; import com.tangosol.net.OperationalContext; +import com.tangosol.net.TopicService; + import com.tangosol.run.xml.XmlElement; import com.tangosol.run.xml.XmlHelper; @@ -162,7 +162,7 @@ public static String getServiceName(XmlElement xmlScheme) sServiceName = InvocationService.TYPE_REMOTE; break; case DefaultConfigurableCacheFactory.SCHEME_PAGED_TOPIC: - sServiceName = CacheService.TYPE_PAGED_TOPIC; + sServiceName = TopicService.TYPE_PAGED_TOPIC; break; } } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/DefaultTopicServiceProxyDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/DefaultTopicServiceProxyDependencies.java new file mode 100644 index 0000000000000..781cf2b4c0c6d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/DefaultTopicServiceProxyDependencies.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.service.extend.proxy; + + +import com.tangosol.config.annotation.Injectable; + +/** + * The DefaultCacheServiceProxyDependencies class provides a default implementation of + * CacheServiceProxyDependencies. + * + * @author Jonathan Knight 2025.01.01 + */ +public class DefaultTopicServiceProxyDependencies + extends DefaultServiceProxyDependencies + implements TopicServiceProxyDependencies + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a DefaultTopicServiceProxyDependencies object. + */ + public DefaultTopicServiceProxyDependencies() + { + this(null); + } + + /** + * Construct a DefaultTopicServiceProxyDependencies object, copying the values from the + * specified TopicServiceProxyDependencies object. + * + * @param deps the dependencies to copy, or null + */ + public DefaultTopicServiceProxyDependencies(TopicServiceProxyDependencies deps) + { + super(deps); + + if (deps != null) + { + m_cbTransferThreshold = deps.getTransferThreshold(); + } + } + + // ----- TopicServiceProxyDependencies methods -------------------------- + + @Override + public long getTransferThreshold() + { + return m_cbTransferThreshold; + } + + /** + * Set the transfer threshold. + * + * @param cbThreshold the transfer threshold + */ + @Injectable("transfer-threshold") + public void setTransferThreshold(long cbThreshold) + { + m_cbTransferThreshold = cbThreshold; + } + + // ----- Object methods ------------------------------------------------- + + @Override + public String toString() + { + return super.toString() + "{TransferThreshold=" + getTransferThreshold() + "}"; + } + + // ----- data members --------------------------------------------------- + + /** + * The transfer threshold. + */ + private long m_cbTransferThreshold = 524288; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/LegacyXmlTopicServiceProxyHelper.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/LegacyXmlTopicServiceProxyHelper.java new file mode 100644 index 0000000000000..38df614aaf7d0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/LegacyXmlTopicServiceProxyHelper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.service.extend.proxy; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Base; + +/** + * LegacyXmlTopicServiceProxyHelper parses XML to populate a + * DefaultTopicServiceProxyDependencies object. + * + * @author Jonathan Knight 2025.01.01 + */ +@Deprecated +public class LegacyXmlTopicServiceProxyHelper + { + /** + * Populate the DefaultTopicServiceProxyDependencies object from the given XML + * configuration. + * + * @param xml the XML parent element that contains the Proxy elements + * @param deps the DefaultTopicServiceProxyDependencies to be populated + * + * @return the DefaultTopicServiceProxyDependencies object that was passed in + */ + public static DefaultTopicServiceProxyDependencies fromXml(XmlElement xml, + DefaultTopicServiceProxyDependencies deps) + { + LegacyXmlServiceProxyHelper.fromXml(xml, deps); + + String sBytes = xml.getSafeElement("transfer-threshold").getString(); + if (sBytes != null && sBytes.length() > 0) + { + try + { + deps.setTransferThreshold(Base.parseMemorySize(sBytes)); + } + catch (RuntimeException e) + { + throw Base.ensureRuntimeException(e, + "illegal \"transfer-threshold\" value: " + sBytes); + } + } + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/TopicServiceProxyDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/TopicServiceProxyDependencies.java new file mode 100644 index 0000000000000..3db1ec5e5604d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/proxy/TopicServiceProxyDependencies.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.service.extend.proxy; + +/** + * The {@link TopicServiceProxyDependencies} interface provides a TopicServiceProxy + * with its external dependencies. + * + * @author Jonathan Knight 2025.01.01 + */ +public interface TopicServiceProxyDependencies + extends ServiceProxyDependencies + { + /** + * Return the approximate maximum number of bytes transferred by a partial response. + * Results that can be streamed are returned to the requester as a sequence of response + * messages containing a portion of the total result. + * Each of these response messages will be approximately no larger than the configured size. + * + * @return the transfer threshold + */ + long getTransferThreshold(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/DefaultRemoteTopicServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/DefaultRemoteTopicServiceDependencies.java new file mode 100644 index 0000000000000..18d9df87ea9d8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/DefaultRemoteTopicServiceDependencies.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.service.extend.remote; + +/** + * The DefaultRemoteTopicServiceDependencies class provides a default implementation of + * RemoteTopicServiceDependencies. + * + * @author Jonathan Knight 2025.01.01 + */ +public class DefaultRemoteTopicServiceDependencies + extends DefaultRemoteServiceDependencies + implements RemoteTopicServiceDependencies + { + /** + * Construct a DefaultRemoteTopicServiceDependencies object. + */ + public DefaultRemoteTopicServiceDependencies() + { + this(null); + } + + /** + * Construct a DefaultRemoteTopicServiceDependencies object, copying the values from + * the specified RemoteTopicServiceDependencies object. + * + * @param deps the dependencies to copy, or null + */ + public DefaultRemoteTopicServiceDependencies(RemoteTopicServiceDependencies deps) + { + super(deps); + } + + // ----- RemoteTopicServiceDependencies methods ------------------------- + + /** + * Validate the supplied dependencies. + * + * @throws IllegalArgumentException if the dependencies are not valid + * + * @return this object + */ + @Override + public DefaultRemoteServiceDependencies validate() + { + super.validate(); + + return this; + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return super.toString() + + "{" + + "}"; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/LegacyXmlRemoteTopicServiceHelper.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/LegacyXmlRemoteTopicServiceHelper.java new file mode 100644 index 0000000000000..df242a8d1ecfd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/LegacyXmlRemoteTopicServiceHelper.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.service.extend.remote; + +import com.tangosol.net.OperationalContext; +import com.tangosol.run.xml.XmlElement; + +/** + * LegacyXmlRemoteTopicServiceHelper parses XML to populate a + * DefaultRemoteTopicServiceDependencies object. + * + * @author Jonathan Knight 2025.01.01 + */ +@Deprecated +public class LegacyXmlRemoteTopicServiceHelper + { + /** + * Populate the DefaultRemoteTopicServiceDependencies object from the given XML + * configuration. + * + * @param xml the XML parent element that contains the RemoteTopicService elements + * @param deps the DefaultRemoteTopicServiceDependencies to be populated + * @param ctx the OperationalContext + * @param loader the class loader for the current context + * + * @return the DefaultRemoteTopicServiceDependencies object that was passed in + */ + public static DefaultRemoteTopicServiceDependencies fromXml(XmlElement xml, + DefaultRemoteTopicServiceDependencies deps, OperationalContext ctx, + ClassLoader loader) + { + LegacyXmlRemoteServiceHelper.fromXml(xml, deps, ctx, loader); + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/RemoteTopicServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/RemoteTopicServiceDependencies.java new file mode 100644 index 0000000000000..b446bba0aeb78 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/extend/remote/RemoteTopicServiceDependencies.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.service.extend.remote; + +/** + * The RemoteTopicServiceDependencies interface provides a RemoteTopicService with its external + * dependencies. + * + * @author Jonathan Knight 2025.01.01 + */ +public interface RemoteTopicServiceDependencies + extends RemoteServiceDependencies + { + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/DefaultProxyServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/DefaultProxyServiceDependencies.java index cc4e2061f4790..e81177307247b 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/DefaultProxyServiceDependencies.java +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/DefaultProxyServiceDependencies.java @@ -1,8 +1,8 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. + * https://oss.oracle.com/licenses/upl. */ package com.tangosol.internal.net.service.grid; @@ -13,8 +13,9 @@ import com.tangosol.internal.net.service.extend.proxy.CacheServiceProxyDependencies; import com.tangosol.internal.net.service.extend.proxy.InvocationServiceProxyDependencies; -import com.tangosol.internal.net.service.peer.acceptor.AcceptorDependencies; +import com.tangosol.internal.net.service.extend.proxy.TopicServiceProxyDependencies; +import com.tangosol.internal.net.service.peer.acceptor.AcceptorDependencies; import com.tangosol.internal.net.service.peer.acceptor.DefaultTcpAcceptorDependencies; import com.tangosol.util.Base; @@ -58,6 +59,7 @@ public DefaultProxyServiceDependencies(ProxyServiceDependencies deps) m_acceptorDependencies = deps.getAcceptorDependencies(); m_bldrLoadBalancer = deps.getLoadBalancerBuilder(); m_cacheServiceProxyDependencies = deps.getCacheServiceProxyDependencies(); + m_topicServiceProxyDependencies = deps.getTopicServiceProxyDependencies(); m_invocationServiceProxyDependencies = deps.getInvocationServiceProxyDependencies(); } } @@ -77,8 +79,8 @@ public void setActionPolicyBuilder(ActionPolicyBuilder builder) // ----- ProxyServiceDependencies interface ----------------------------- /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ @Override public AcceptorDependencies getAcceptorDependencies() { @@ -116,6 +118,26 @@ public void setCacheServiceProxyDependencies(CacheServiceProxyDependencies deps) m_cacheServiceProxyDependencies = deps; } + /** + * {@inheritDoc} + */ + @Override + public TopicServiceProxyDependencies getTopicServiceProxyDependencies() + { + return m_topicServiceProxyDependencies; + } + + /** + * Set the TopicServiceProxyDependencies. + * + * @param deps the TopicServiceProxyDependencies + */ + @Injectable("proxy-config/topic-service-proxy") + public void setTopicServiceProxyDependencies(TopicServiceProxyDependencies deps) + { + m_topicServiceProxyDependencies = deps; + } + /** * {@inheritDoc} */ @@ -181,6 +203,7 @@ public String toString() + "{AcceptorDependencies=" + getAcceptorDependencies() + ", ActionPolicyBuilder=" + getActionPolicyBuilder() + ", CacheServiceProxyDependencies=" + getCacheServiceProxyDependencies() + + ", TopicServiceProxyDependencies=" + getTopicServiceProxyDependencies() + ", InvocationServiceProxyDependencies=" + getInvocationServiceProxyDependencies() + ", LoadBalancerBuilder=" + getLoadBalancerBuilder() + "}"; } @@ -197,6 +220,12 @@ public String toString() */ private CacheServiceProxyDependencies m_cacheServiceProxyDependencies; + /** + * The TopicServiceProxyDependencies. + */ + private TopicServiceProxyDependencies m_topicServiceProxyDependencies; + + /** * The InvocationServiceProxyDependencies. */ diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/ProxyServiceDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/ProxyServiceDependencies.java index 0e68b771d118a..44d2be3f5bece 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/ProxyServiceDependencies.java +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/service/grid/ProxyServiceDependencies.java @@ -1,15 +1,17 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at - * http://oss.oracle.com/licenses/upl. + * https://oss.oracle.com/licenses/upl. */ package com.tangosol.internal.net.service.grid; import com.tangosol.coherence.config.builder.ActionPolicyBuilder; import com.tangosol.coherence.config.builder.ServiceLoadBalancerBuilder; + import com.tangosol.internal.net.service.extend.proxy.CacheServiceProxyDependencies; import com.tangosol.internal.net.service.extend.proxy.InvocationServiceProxyDependencies; +import com.tangosol.internal.net.service.extend.proxy.TopicServiceProxyDependencies; import com.tangosol.internal.net.service.peer.acceptor.AcceptorDependencies; @@ -45,6 +47,13 @@ public interface ProxyServiceDependencies */ public CacheServiceProxyDependencies getCacheServiceProxyDependencies(); + /** + * Return the CacheServiceProxyDependencies. + * + * @return the CacheServiceProxyDependencies + */ + public TopicServiceProxyDependencies getTopicServiceProxyDependencies(); + /** * Return the InvocationServiceProxyDependencies. * diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemotePublisher.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemotePublisher.java new file mode 100644 index 0000000000000..1a63442c044a1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemotePublisher.java @@ -0,0 +1,397 @@ + +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +// ---- class: com.tangosol.coherence.component.net.extend.RemoteNamedTopic + +package com.tangosol.internal.net.topic; + +import com.tangosol.internal.net.topic.NamedTopicPublisher.PublisherEvent; +import com.tangosol.internal.net.topic.NamedTopicPublisher.PublisherListener; + +import com.tangosol.net.TopicService; + +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.TopicDependencies; + +import com.tangosol.util.Binary; +import com.tangosol.util.Listeners; +import com.tangosol.util.TaskDaemon; + +import java.util.List; + +import java.util.concurrent.CompletableFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import java.util.function.BiConsumer; + +/** + * A base class for remote publishers. + * + * @author Jonathan Knight 2025.01.01 + */ +public abstract class BaseRemotePublisher + implements PublisherConnector + { + // ----- constructors --------------------------------------------------- + + /** + * Create a {@link BaseRemotePublisher}. + * + * @param nId the unique publisher identifier + * @param cChannel the default channel count for the topic + * @param options the publisher options + */ + public BaseRemotePublisher(long nId, int cChannel, Publisher.Option[] options) + { + Publisher.OptionSet optionSet = Publisher.optionsFrom(options); + f_nId = nId; + f_cChannel = optionSet.getChannelCount(cChannel); + } + + // ----- ConnectedPublisher.Connector methods --------------------------- + + @Override + public void close() + { + if (m_daemon != null) + { + m_daemon.stop(); + } + } + + @Override + public boolean isDestroyed() + { + return m_fDestroyed; + } + + @Override + public boolean isReleased() + { + return m_fReleased; + } + + @Override + public long getId() + { + return f_nId; + } + + @Override + public TopicDependencies getTopicDependencies() + { + return m_topicService.getTopicBackingMapManager().getTopicDependencies(m_sTopicName); + } + + @Override + public int getChannelCount() + { + return f_cChannel; + } + + @Override + public void addListener(PublisherListener listener) + { + f_listeners.add(listener); + } + + @Override + public void removeListener(PublisherListener listener) + { + f_listeners.remove(listener); + } + + @Override + public TopicService getTopicService() + { + return m_topicService; + } + + /** + * Getter for property TopicName.

+ */ + @Override + public String getTopicName() + { + return m_sTopicName; + } + + @Override + public long getMaxBatchSizeBytes() + { + if (m_maxBatchSizeBytes <= 0) + { + return getTopicDependencies().getMaxBatchSizeBytes(); + } + return m_maxBatchSizeBytes; + } + + /** + * Set the maximum batch size. + * + * @param maxBatchSizeBytes the maximum batch size + */ + public void setMaxBatchSizeBytes(long maxBatchSizeBytes) + { + m_maxBatchSizeBytes = maxBatchSizeBytes; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Setter for property TopicName.

+ */ + public void setTopicName(String sName) + { + m_sTopicName = sName; + } + + /** + * Setter for property TopicService.

+ */ + public void setTopicService(TopicService serviceTopic) + { + m_topicService = serviceTopic; + } + + /** + * Obtain the task daemon. + * + * @return the task daemon + */ + public TaskDaemon ensureTaskDaemon() + { + TaskDaemon daemon = m_daemon; + if (daemon == null) + { + f_lock.lock(); + try + { + daemon = m_daemon; + if (daemon == null) + { + daemon = m_daemon = new TaskDaemon(getClass().getSimpleName() + ":" + m_sTopicName + ":" + f_nId); + } + } + finally + { + f_lock.unlock(); + } + } + return daemon; + } + + /** + * Dispatch a publisher event. + * + * @param type the event type + */ + public void dispatchEvent(PublisherEvent.Type type) + { + PublisherEvent event = new PublisherEvent(this, type); + event.dispatch(f_listeners); + } + + /** + * Dispatch a publisher event. + * + * @param type the event type + * @param anChannel the event channels + */ + public void dispatchEvent(PublisherEvent.Type type, int[] anChannel) + { + PublisherEvent event = new PublisherEvent(this, type, anChannel); + event.dispatch(f_listeners); + } + + protected void dispatchEvent(PublisherEvent evt) + { + switch (evt.getType()) + { + case Destroyed: + m_fDestroyed = true; + break; + case Released: + m_fReleased = true; + break; + } + evt.dispatch(f_listeners); + } + + // ----- inner class: ChannelConnector ---------------------------------- + + /** + * A base class for a publisher channel connector. + */ + protected abstract class BaseChannelConnector + implements PublisherChannelConnector + { + /** + * Create a {@link BaseChannelConnector}. + * + * @param nId the unique identifier for the publisher + * @param nChannel the channel identifier for this connector + */ + protected BaseChannelConnector(long nId, int nChannel) + { + f_nId = nId; + f_nChannel = nChannel; + } + + @Override + public boolean isActive() + { + return BaseRemotePublisher.this.isActive(); + } + + @Override + public int getChannel() + { + return f_nChannel; + } + + @Override + public void close() + { + } + + @Override + public void ensureConnected() + { + BaseRemotePublisher.this.ensureConnected(); + } + + @Override + public String getTopicName() + { + return m_sTopicName; + } + + @Override + public CompletionStage initialize() + { + return f_completedFuture; + } + + @Override + public void offer(Object oCookie, List listBinary, int nNotifyPostFull, BiConsumer handler) + { + try + { + offerInternal(listBinary, nNotifyPostFull) + .handleAsync((result, error) -> + { + handler.accept(result, error); + return null; + }, ensureTaskDaemon()); + } + catch (Throwable t) + { + handler.accept(null, t); + } + } + + /** + * Publish the specified list of binary values. + * + * @param listBinary the list of values to publish + * @param nNotifyPostFull {@code true} to configure notifications for this publisher when the topic is full + */ + protected abstract CompletionStage offerInternal(List listBinary, int nNotifyPostFull); + + @Override + public CompletionStage prepareOfferRetry(Object oCookie) + { + return f_completedFuture; + } + + @Override + public TopicDependencies getTopicDependencies() + { + return BaseRemotePublisher.this.getTopicDependencies(); + } + + @Override + public TopicService getTopicService() + { + return m_topicService; + } + + // ----- data members --------------------------------------------------- + + /** + * A unique identifier for this publisher. + */ + protected final long f_nId; + + /** + * The channel identifier. + */ + protected final int f_nChannel; + } + + // ----- data members --------------------------------------------------- + + /** + * The configured channel count. + */ + private final int f_cChannel; + + /** + * The publisher identifier. + */ + private final long f_nId; + + /** + * Property Listeners + */ + private final Listeners f_listeners = new Listeners(); + + /** + * Property TopicName + */ + private String m_sTopicName; + + /** + * Property TopicService + */ + private TopicService m_topicService; + + /** + * The daemon to use to complete async tasks. + */ + private TaskDaemon m_daemon; + + /** + * The lock to synchronize state. + */ + private final Lock f_lock = new ReentrantLock(); + + /** + * The maximum batch size for the publisher. + */ + private long m_maxBatchSizeBytes = -1; + + /** + * A completed void {@link CompletableFuture}. + */ + private static final CompletableFuture f_completedFuture = CompletableFuture.completedFuture(null); + + /** + * A flag to indicate that this subscriber's topic was released. + */ + private boolean m_fReleased; + + /** + * A flag to indicate that this subscriber's topic was destroyed. + */ + private boolean m_fDestroyed; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemoteSubscriber.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemoteSubscriber.java new file mode 100644 index 0000000000000..8431d70189dc2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/BaseRemoteSubscriber.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.internal.net.topic; + +import com.oracle.coherence.common.base.Logger; + +import com.tangosol.internal.net.topic.NamedTopicSubscriber.TopicChannel; + +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberGroupId; +import com.tangosol.internal.net.topic.impl.paged.model.SubscriberId; + +import com.tangosol.net.topic.Position; +import com.tangosol.net.topic.Subscriber; +import com.tangosol.net.topic.Subscriber.CommitResult; + +import com.tangosol.util.Listeners; +import com.tangosol.util.TaskDaemon; + +import java.util.concurrent.CompletableFuture; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * A base class for a client side remote {@link SubscriberConnector}. + * + * @param the type of element the subscriber receives + * + * @author Jonathan Knight 2025.01.01 + */ +public abstract class BaseRemoteSubscriber + implements SubscriberConnector + { + /** + * Create a {@link BaseRemoteSubscriber}. + * + * @param sTopicName the topic name + * @param subscriberId the subscriber identifier + * @param groupId the subscriber group identifier + */ + protected BaseRemoteSubscriber(String sTopicName, SubscriberId subscriberId, SubscriberGroupId groupId) + { + f_sTopicName = sTopicName; + f_subscriberId = subscriberId; + f_subscriberGroupId = groupId; + } + + @Override + public void postConstruct(ConnectedSubscriber subscriber) + { + } + + @Override + public void addListener(SubscriberListener listener) + { + f_listeners.add(listener); + } + + @Override + public void removeListener(SubscriberListener listener) + { + f_listeners.remove(listener); + } + + @Override + public boolean isGroupDestroyed() + { + return m_fGroupDestroyed; + } + + @Override + public boolean isDestroyed() + { + return m_fDestroyed; + } + + @Override + public boolean isReleased() + { + return m_fReleased; + } + + @Override + public TopicChannel createChannel(ConnectedSubscriber subscriber, int nChannel) + { + return new RemoteChannel(nChannel); + } + + @Override + public long getConnectionTimestamp() + { + return m_connectionTimestamp; + } + + @Override + public long getSubscriptionId() + { + return m_subscriptionId; + } + + @Override + public CompletableFuture receive(ConnectedSubscriber subscriber, int nChannel, + Position headPosition, long lVersion, ReceiveHandler handler) + { + CompletableFuture future = new CompletableFuture<>(); + try + { + SimpleReceiveResult result = receiveInternal(nChannel, headPosition, lVersion); + handler.onReceive(lVersion, result, null, null); + subscriber.setChannelHeadIfHigher(nChannel, result.getHead()); + future.complete(result); + } + catch (Throwable t) + { + try + { + handler.onReceive(lVersion, null, t, null); + } + catch (Exception e) + { + Logger.err(e); + } + future.completeExceptionally(t); + } + return future; + } + + /** + * Send a receive request to the proxy. + * + * @param nChannel the channel identifier + * @param headPosition the heap position of the channel + * @param lVersion the channel version + * + * @return the result of the receive request + */ + protected abstract SimpleReceiveResult receiveInternal(int nChannel, Position headPosition, long lVersion); + + @Override + public CompletableFuture commit(ConnectedSubscriber subscriber, int nChannel, Position position) + { + CompletableFuture future = new CompletableFuture<>(); + try + { + commitInternal(nChannel, position, (result, head) -> + { + subscriber.setChannelHeadIfHigher(nChannel, head); + future.complete(result); + }); + } + catch (Throwable t) + { + future.completeExceptionally(t); + } + return future; + } + + /** + * Send a commit request to the proxy. + * + * @param nChannel the channel to commit + * @param position the position to commit + * @param handler the {@link CommitHandler} to receive the results + */ + protected abstract void commitInternal(int nChannel, Position position, CommitHandler handler); + + @Override + public void heartbeat(ConnectedSubscriber subscriber, boolean fAsync) + { + if (f_subscriberGroupId.isDurable()) + { + sendHeartbeat(fAsync); + } + } + + protected abstract void sendHeartbeat(boolean fAsync); + + @Override + public String getTypeName() + { + return getClass().getSimpleName(); + } + + @Override + public void onInitialized(ConnectedSubscriber subscriber) + { + } + + /** + * Return the {@link SubscriberId} for this subscriber. + * + * @return the {@link SubscriberId} for this subscriber + */ + public SubscriberId getSubscriberId() + { + return f_subscriberId; + } + + /** + * Return the {@link SubscriberGroupId} for this subscriber. + * + * @return the {@link SubscriberGroupId} for this subscriber + */ + public SubscriberGroupId getSubscriberGroupId() + { + return f_subscriberGroupId; + } + + @Override + public void close() + { + if (m_daemon != null) + { + m_daemon.stop(); + } + } + + // ----- helper methods ------------------------------------------------- + + /** + * Obtain the task daemon. + * + * @return the task daemon + */ + protected TaskDaemon ensureTaskDaemon() + { + TaskDaemon daemon = m_daemon; + if (daemon == null) + { + f_lock.lock(); + try + { + daemon = m_daemon; + if (daemon == null) + { + daemon = m_daemon = new TaskDaemon(getClass().getSimpleName() + ":" + + f_sTopicName + ":" + f_subscriberId.getId()); + } + } + finally + { + f_lock.unlock(); + } + } + return daemon; + } + + public void onDisconnected() + { + m_subscriptionId = 0L; + dispatchEvent(new SubscriberEvent(this, SubscriberEvent.Type.Disconnected)); + } + + /** + * Dispatch a {@link SubscriberEvent}. + * + * @param event the event to dispatch + */ + public void dispatchEvent(SubscriberEvent event) + { + switch (event.getType()) + { + case GroupDestroyed: + m_fGroupDestroyed = true; + m_subscriptionId = 0L; + break; + case Destroyed: + m_fDestroyed = true; + m_subscriptionId = 0L; + break; + case Released: + m_fReleased = true; + m_subscriptionId = 0L; + break; + } + event.dispatch(f_listeners); + } + + // ----- inner class: CommitHandler ------------------------------------- + + /** + * A handler for commit results. + */ + public interface CommitHandler + { + void committed(CommitResult result, Position head); + } + + // ----- inner class: RemoteChannel ------------------------------------- + + /** + * Channel is a data structure which represents the state of a channel as known + * by this subscriber. + */ + public static class RemoteChannel + extends TopicChannel + implements Subscriber.Channel + { + public RemoteChannel(int nChannel) + { + m_head = Position.EMPTY_POSITION; + f_nChannel = nChannel; + } + + @Override + public int getId() + { + return f_nChannel; + } + + // ----- Object methods --------------------------------------------- + + public String toString() + { + return "Channel=" + f_nChannel + + ", owned=" + m_fOwned + + ", empty=" + m_fEmpty + + ", version=" + m_lVersion.get() + + ", head=" + m_head + + ", polls=" + m_cPolls + + ", received=" + m_cReceived.getCount() + + ", committed=" + m_cCommited + + ", first=" + m_firstPolled + + ", firstTimestamp=" + m_firstPolledTimestamp + + ", last=" + m_lastPolled + + ", lastTimestamp=" + m_lastPolledTimestamp + + ", contended=" + m_fContended; + } + + // ----- data members ----------------------------------------------- + + private final int f_nChannel; + } + + // ----- data members --------------------------------------------------- + + /** + * The name of the topic. + */ + protected final String f_sTopicName; + + /** + * The subscriber identifier. + */ + protected final SubscriberId f_subscriberId; + + /** + * The subscriber group identifier. + */ + protected final SubscriberGroupId f_subscriberGroupId; + + /** + * The daemon to use to complete async tasks. + */ + protected TaskDaemon m_daemon; + + /** + * The lock to synchronize state. + */ + protected final Lock f_lock = new ReentrantLock(); + + /** + * The registered {@link SubscriberListener} instances. + */ + protected final Listeners f_listeners = new Listeners(); + + /** + * A flag to indicate that this subscriber's group was destroyed. + */ + protected boolean m_fGroupDestroyed = false; + + /** + * A flag to indicate that this subscriber's topic was released. + */ + protected boolean m_fReleased; + + /** + * A flag to indicate that this subscriber's topic was destroyed. + */ + protected boolean m_fDestroyed; + + /** + * The unique identifier for the subscriber group. + */ + protected long m_subscriptionId; + + /** + * The subscriber's connection timestamp. + */ + protected volatile long m_connectionTimestamp; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/DefaultTopicBackingMapManager.java b/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/DefaultTopicBackingMapManager.java new file mode 100644 index 0000000000000..278f45fa4a927 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/internal/net/topic/DefaultTopicBackingMapManager.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ +package com.tangosol.internal.net.topic; + +import com.tangosol.coherence.config.scheme.NamedTopicScheme; +import com.tangosol.coherence.config.scheme.TopicScheme; +import com.tangosol.config.expression.ParameterResolver; + + +import com.tangosol.net.ExtensibleConfigurableCacheFactory; + +import com.tangosol.net.topic.TopicBackingMapManager; +import com.tangosol.net.topic.TopicDependencies; + +import java.util.HashMap; +import java.util.Map; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * The {@link com.tangosol.net.topic.TopicBackingMapManager} for a gRPC topic. + * + * @author Jonathan Knight 2025.01.01 + */ +@SuppressWarnings("rawtypes") +public class DefaultTopicBackingMapManager + extends TopicBackingMapManager + { + // ----- constructors --------------------------------------------------- + + /** + * Create a {@link DefaultTopicBackingMapManager}. + * + * @param eccf the owning {@link ExtensibleConfigurableCacheFactory} + */ + public DefaultTopicBackingMapManager(ExtensibleConfigurableCacheFactory eccf) + { + super(eccf); + } + + // ----- TopicBackingMapManager methods --------------------------------- + + @Override + public NamedTopicScheme findTopicScheme(String sName) + { + return getCacheFactory().getCacheConfig().findSchemeByTopicName(sName); + } + + @Override + public TopicDependencies getTopicDependencies(String sTopicName) + { + TopicDependencies deps = m_mapDeps.get(sTopicName); + if (deps == null) + { + m_lock.lock(); + try + { + deps = m_mapDeps.computeIfAbsent(sTopicName, this::createTopicDependencies); + } + finally + { + m_lock.unlock(); + } + } + return deps; + } + + @Override + public Map instantiateBackingMap(String sName) + { + throw new UnsupportedOperationException(); + } + + @Override + public void releaseBackingMap(String sName, Map map) + { + throw new UnsupportedOperationException(); + } + + // ----- helper methods ------------------------------------------------- + + private TopicDependencies createTopicDependencies(String sName) + { + ClassLoader loader = getContextClassLoader(); + ParameterResolver resolver = getCacheFactory().getParameterResolver(sName, loader, null); + TopicScheme scheme = findTopicScheme(sName); + if (scheme == null) + { + throw new IllegalStateException("Cannot find paged-topic-scheme for topic " + sName); + } + return scheme.createConfiguration(resolver, loader); + } + + // ----- data members --------------------------------------------------- + + /** + * The lock to use to synchronize access to internal state. + */ + private final Lock m_lock = new ReentrantLock(true); + + /** + * A map of {@link TopicDependencies} keyed by topic name. + */ + private final Map m_mapDeps = new HashMap<>(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/net/CacheService.java b/prj/coherence-core/src/main/java/com/tangosol/net/CacheService.java index fcefc2be67619..afa3676ab5c28 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/net/CacheService.java +++ b/prj/coherence-core/src/main/java/com/tangosol/net/CacheService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -188,7 +188,7 @@ public interface CacheAction * * @see Cluster#ensureService(String, String) */ - public static final String TYPE_PAGED_TOPIC = "PagedTopic"; + public static final String TYPE_PAGED_TOPIC = TopicService.TYPE_PAGED_TOPIC; /** * LocalCache service type constant. diff --git a/prj/coherence-core/src/main/java/com/tangosol/net/ClusterDependencies.java b/prj/coherence-core/src/main/java/com/tangosol/net/ClusterDependencies.java index b5bb2948e24ed..cdf098294ca71 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/net/ClusterDependencies.java +++ b/prj/coherence-core/src/main/java/com/tangosol/net/ClusterDependencies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -8,10 +8,12 @@ import com.tangosol.coherence.config.builder.ActionPolicyBuilder; +import com.tangosol.coherence.config.builder.MapBuilder; import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; import com.tangosol.coherence.config.builder.ServiceFailurePolicyBuilder; import com.tangosol.coherence.config.builder.SocketProviderBuilder; +import com.tangosol.config.expression.ParameterResolver; import com.tangosol.io.SerializerFactory; import com.tangosol.io.WrapperStreamFactory; @@ -698,7 +700,7 @@ public interface ClusterDependencies /** * A provider of nw service instances. */ - public interface ServiceProvider + public interface ServiceProvider { /** * Create a new instance of a service. @@ -710,13 +712,43 @@ public interface ServiceProvider */ Service createService(String sName, Cluster cluster); - ServiceProvider NULL_IMPLEMENTATION = new ServiceProvider() + /** + * Create a new instance of a topic service. + * + * @param resolver the {@link ParameterResolver} + * @param deps the {@link MapBuilder.Dependencies} + * + * @return the new service instance + */ + S ensureConfiguredService(ParameterResolver resolver, MapBuilder.Dependencies deps); + + /** + * Return a null-implementation of a {@link ServiceProvider}. + * + * @return a null-implementation of a {@link ServiceProvider} + */ + @SuppressWarnings("unchecked") + static ServiceProvider nullImplementation() + { + return (ServiceProvider) NULL_IMPLEMENTATION; + } + + /** + * A null-implementation of a {@link ServiceProvider}. + */ + ServiceProvider NULL_IMPLEMENTATION = new ServiceProvider<>() { @Override public Service createService(String sName, Cluster cluster) { return null; } + + @Override + public Service ensureConfiguredService(ParameterResolver resolver, MapBuilder.Dependencies deps) + { + return null; + } }; } diff --git a/prj/coherence-core/src/main/java/com/tangosol/net/ExtensibleConfigurableCacheFactory.java b/prj/coherence-core/src/main/java/com/tangosol/net/ExtensibleConfigurableCacheFactory.java index e64823616cb3e..436ed572ac59b 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/net/ExtensibleConfigurableCacheFactory.java +++ b/prj/coherence-core/src/main/java/com/tangosol/net/ExtensibleConfigurableCacheFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -63,6 +63,8 @@ import com.tangosol.config.xml.DocumentProcessor; +import com.tangosol.internal.net.topic.DefaultTopicBackingMapManager; + import com.tangosol.io.BinaryStore; import com.tangosol.io.ClassLoaderAware; @@ -462,7 +464,6 @@ C ensureCollectionInternal(String sName, Class clsCollection, ClassLoader loa // find the scheme for the collection ServiceScheme serviceScheme = f_cacheConfig.findSchemeBySchemeName(mapping.getSchemeName()); - if (serviceScheme == null) { String sMsg = String.format("ensureCollection cannot find service scheme %s for mapping %s", @@ -471,7 +472,7 @@ C ensureCollectionInternal(String sName, Class clsCollection, ClassLoader loa throw new IllegalArgumentException(sMsg); } - // there are instances of sibling caches for different + // there are instances of sibling collections for different // class loaders; check for and clear invalid references Collection colHashCode = store.clearInactiveRefs(sName); @@ -777,6 +778,10 @@ public Parameter resolve(String sName) cacheService.setBackingMapManager(mgr); } } + else if (service instanceof TopicService) + { + ((TopicService) service).setTopicBackingMapManager(new DefaultTopicBackingMapManager(this)); + } startService(service); } diff --git a/prj/coherence-core/src/main/java/com/tangosol/util/ExternalizableHelper.java b/prj/coherence-core/src/main/java/com/tangosol/util/ExternalizableHelper.java index 2eb7991c22115..22b96f055454c 100644 --- a/prj/coherence-core/src/main/java/com/tangosol/util/ExternalizableHelper.java +++ b/prj/coherence-core/src/main/java/com/tangosol/util/ExternalizableHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. * * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. @@ -56,6 +56,7 @@ import com.tangosol.net.NamedCache; +import com.tangosol.net.NamedCollection; import com.tangosol.net.cache.CacheMap; import com.tangosol.run.xml.SimpleParser; @@ -4446,8 +4447,22 @@ public static boolean isSerializerCompatible( public static void reportIncompatibleSerializers(NamedCache cache, String sService, Serializer serializer) { - Logger.warn("The serializer used by cache \"" + cache.getCacheName() + "\" (" - + cache.getCacheService().getSerializer() + ") is incompatible with the" + reportIncompatibleSerializers(cache, "cache", sService, serializer); + } + + /** + * Log the message explaining the serializer incompatibility between the + * specified cache and a service. + * + * @param collection the NamedCollection reference + * @param sService the service name + * @param serializer the serializer used by the service + */ + public static void reportIncompatibleSerializers(NamedCollection collection, String sType, + String sService, Serializer serializer) + { + Logger.warn("The serializer used by " + sType + " \"" + collection.getName() + "\" (" + + collection.getService().getSerializer() + ") is incompatible with the" + " serializer configured for service \"" + sService + "\" (" + serializer + "); therefore, cached keys and values will be" diff --git a/prj/coherence-core/src/main/resources/coherence-cache-config.xsd b/prj/coherence-core/src/main/resources/coherence-cache-config.xsd index 3aea0f9173335..7c68fe64c68df 100644 --- a/prj/coherence-core/src/main/resources/coherence-cache-config.xsd +++ b/prj/coherence-core/src/main/resources/coherence-cache-config.xsd @@ -1,9 +1,9 @@ + + - The grpc-cache-scheme element contains the + The remote-grpc-cache-scheme element contains the configuration info necessary to use a clustered cache from outside the cluster, connecting using gRPC. @@ -910,13 +912,73 @@ Setting this value to $DEFAULT$ will use the default (empty) scope on the proxy server, this is useful where the parent scope-name is configured for this configuration as the remote-scope-name cannot be set to an empty value in XML. - Used in: remote-grpc-cache-scheme - + + + + The remote-topic-scheme element contains the + configuration info necessary to use a clustered + topic from outside the cluster. + + Used in: caching-schemes + + + + + + + + + + + + + + + + + + + + + + The grpc-cache-scheme element contains the + configuration info necessary to use a clustered + topic from outside the cluster, connecting using gRPC. + + Used in: caching-schemes + + + + + + + + + + + + + + + + + + + + + + + + + + The proxy-scheme element contains the configuration diff --git a/prj/coherence-core/src/main/resources/com/oracle/coherence/defaults/coherence-cache-config.xml b/prj/coherence-core/src/main/resources/com/oracle/coherence/defaults/coherence-cache-config.xml index c6833da797d42..a8437aa370956 100644 --- a/prj/coherence-core/src/main/resources/com/oracle/coherence/defaults/coherence-cache-config.xml +++ b/prj/coherence-core/src/main/resources/com/oracle/coherence/defaults/coherence-cache-config.xml @@ -1,6 +1,6 @@ + + topic-remote + RemoteTopic + + Proxy + + + + + topic-remote-fixed + RemoteTopic + + + + + +

+ + + + + + + + + + topic-grpc + ${coherence.scope} + RemoteGrpcTopic + + + + +
+ + + + + + + + + + topic-grpc-fixed + ${coherence.scope} + RemoteGrpcTopic + + + + +
+ + + + + + + - topic-server + topic-direct ${coherence.service.name Partitioned}Topic true ${coherence.distributed.partitioncount 257} diff --git a/prj/coherence-core/src/main/resources/tangosol-coherence.xml b/prj/coherence-core/src/main/resources/tangosol-coherence.xml index 5b06d70a2f99f..bb1633b6d1c66 100644 --- a/prj/coherence-core/src/main/resources/tangosol-coherence.xml +++ b/prj/coherence-core/src/main/resources/tangosol-coherence.xml @@ -1,6 +1,6 @@ + + + + + remote-invocation + RemoteInvocation + Proxy + + + diff --git a/prj/test/functional/topics/src/main/resources/simple-persistence-bdb-cache-config.xml b/prj/test/functional/topics/src/main/resources/simple-persistence-bdb-cache-config.xml index 00bfcc9ca9128..1682e7b123085 100644 --- a/prj/test/functional/topics/src/main/resources/simple-persistence-bdb-cache-config.xml +++ b/prj/test/functional/topics/src/main/resources/simple-persistence-bdb-cache-config.xml @@ -1,6 +1,6 @@ + + + + + * + cache-scheme + + + + + + simple-persistent-topic-* + client-scheme + String + + + simple-persistent-retained-topic-* + client-scheme + String + + + retain-values + true + + + + + simple-transient-topic-* + client-scheme + String + + + transient + true + + + + + simple-archiver + client-scheme + int + + + + + + client-scheme + Remote + PofProxy + + pof + + + + + cache-scheme + RemoteCache + PofProxy + + pof + + + + diff --git a/prj/test/functional/topics/src/main/resources/topics-calculator-config.xml b/prj/test/functional/topics/src/main/resources/topics-calculator-config.xml index 9a1b99526b57d..c3802a10059df 100644 --- a/prj/test/functional/topics/src/main/resources/topics-calculator-config.xml +++ b/prj/test/functional/topics/src/main/resources/topics-calculator-config.xml @@ -1,6 +1,6 @@ + + + + + pof* + pof-scheme + + + + java* + java-scheme + + + + + + pof-scheme + PofRemote + PofProxy + + pof + + + + + java-scheme + JavaRemote + JavaProxy + + java + + + + diff --git a/prj/test/functional/topics/src/main/resources/topics-channel-config.xml b/prj/test/functional/topics/src/main/resources/topics-channel-config.xml index 78d4fdf942a2c..5055b79ee9a3b 100644 --- a/prj/test/functional/topics/src/main/resources/topics-channel-config.xml +++ b/prj/test/functional/topics/src/main/resources/topics-channel-config.xml @@ -1,6 +1,6 @@