diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml new file mode 100644 index 0000000..1dbba3a --- /dev/null +++ b/.github/workflows/build-dapr-tls.yaml @@ -0,0 +1,84 @@ +# Copyright 2024, 2025 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence Go Client GitHub Actions CI build DAPR TLS +# --------------------------------------------------------------------------- +name: CI DAPR TLS + +on: + workflow_dispatch: + push: + branches: + - '*' + schedule: + # Every day at midnight + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 25.09-SNAPSHOT + - 25.03.1 + go-version: + - 1.24.x + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Images + shell: bash + run: | + docker pull gcr.io/distroless/java17-debian12 + + - name: Set up JDK 17 for Build + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + + - name: Test DAPR + env: + COH_VERSION: ${{ matrix.coherenceVersion }} + shell: bash + run: | + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean certs generate-proto generate-proto-v1 build-test-images test-cluster-startup test-dapr-tls + make test-cluster-shutdown + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.go-version }}-${{ matrix.coherenceVersion }} + path: build/_output/test-logs diff --git a/.github/workflows/build-dapr.yaml b/.github/workflows/build-dapr.yaml new file mode 100644 index 0000000..c7c67bc --- /dev/null +++ b/.github/workflows/build-dapr.yaml @@ -0,0 +1,85 @@ +# Copyright 2024, 2025 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence Go Client GitHub Actions CI build DAPR +# --------------------------------------------------------------------------- +name: CI DAPR + +on: + workflow_dispatch: + push: + branches: + - '*' + schedule: + # Every day at midnight + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 25.09-SNAPSHOT + - 25.03.1 + go-version: + - 1.24.x + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Images + shell: bash + run: | + docker pull gcr.io/distroless/java17-debian12 + + - name: Set up JDK 17 for Build + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + + - name: Test DAPR + env: + COH_VERSION: ${{ matrix.coherenceVersion }} + shell: bash + run: | + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean generate-proto generate-proto-v1 build-test-images test-cluster-startup + make test-dapr + make test-cluster-shutdown + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.go-version }}-${{ matrix.coherenceVersion }} + path: build/_output/test-logs diff --git a/.github/workflows/build-topics.yaml b/.github/workflows/build-topics.yaml new file mode 100644 index 0000000..b2a7db5 --- /dev/null +++ b/.github/workflows/build-topics.yaml @@ -0,0 +1,83 @@ +# Copyright 2024, 2025 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence Go Client GitHub Actions CI build Topics +# --------------------------------------------------------------------------- +name: CI Topics + +on: + workflow_dispatch: + push: + branches: + - '*' + schedule: + # Every day at midnight + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 25.09-SNAPSHOT + go-version: + - 1.23.x + - 1.24.x + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Images + shell: bash + run: | + docker pull gcr.io/distroless/java17-debian12 + + - name: Set up JDK 17 for Build + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + + - name: E2E Topics Tests + env: + COH_VERSION: ${{ matrix.coherenceVersion }} + shell: bash + run: | + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 INCLUDE_LONG_RUNNING=true PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone-topics + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.go-version }}-${{ matrix.coherenceVersion }} + path: build/_output/test-logs diff --git a/Makefile b/Makefile index d80d1c4..1e7fafb 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,14 @@ # ---------------------------------------------------------------------------------------------------------------------- # This is the version of the coherence-go-client -VERSION ?=2.3.1 +VERSION ?=2.4.0 CURRDIR := $(shell pwd) USER_ID := $(shell echo "`id -u`:`id -g`") override BUILD_BIN := $(CURRDIR)/bin -override PROTO_DIR := $(CURRDIR)/etc/proto +override PROTO_DIR := $(CURRDIR)/etc/proto override PROTOV1_DIR := $(CURRDIR)/etc/proto-v1 +override PROTOTOPICS_DIR := $(CURRDIR)/etc/topics # ---------------------------------------------------------------------------------------------------------------------- # Set the location of various build tools @@ -25,6 +26,8 @@ override PROTO_OUT := $(CURRDIR)/proto override BUILD_TARGETS := $(BUILD_OUTPUT)/targets override TEST_LOGS_DIR := $(BUILD_OUTPUT)/test-logs override COVERAGE_DIR := $(BUILD_OUTPUT)/coverage +override DAPR_DIR := $(BUILD_OUTPUT)/dapr-test +override DAPR_TEST_DIR := $(CURRDIR)/test/dapr override COPYRIGHT_JAR := glassfish-copyright-maven-plugin-2.4.jar override BUILD_CERTS := $(CURRDIR)/test/utils/certs override ENV_FILE := test/utils/.env @@ -240,23 +243,30 @@ ifeq ($(SKIP_PROTO_GENERATION),true) @echo "Skipping proto generation..." else mkdir -p $(PROTOV1_DIR) || true - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/proxy_service_messages_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/proxy_service_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/common_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/common_messages_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/cache_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/cache_service_messages_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/queue_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/queue_service_messages_v1.proto + mkdir -p $(PROTOTOPICS_DIR) || true + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/proxy_service_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/proxy_service_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/common_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/common_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/cache_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/cache_service_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/queue_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/queue_service_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOTOPICS_DIR)/topic_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/topic_service_messages_v1.proto echo "" >> $(PROTOV1_DIR)/proxy_service_messages_v1.proto echo "" >> $(PROTOV1_DIR)/proxy_service_v1.proto echo "" >> $(PROTOV1_DIR)/common_messages_v1.proto echo "" >> $(PROTOV1_DIR)/cache_service_messages_v1.proto echo "" >> $(PROTOV1_DIR)/queue_service_messages_v1.proto + echo "" >> $(PROTOTOPICS_DIR)/topic_service_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/proxy_service_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/proxy_service_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/common_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/cache_service_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/queue_service_messages_v1.proto - mkdir ./proto/v1 || true - $(TOOLS_BIN)/protoc --proto_path=./etc/proto-v1 --go_out=./proto/v1 --go_opt=paths=source_relative --go-grpc_out=./proto/v1 --go-grpc_opt=paths=source_relative etc/proto-v1/proxy_service_messages_v1.proto etc/proto-v1/proxy_service_v1.proto etc/proto-v1/common_messages_v1.proto etc/proto-v1/cache_service_messages_v1.proto etc/proto-v1/queue_service_messages_v1.proto + echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1/topics";' >> $(PROTOTOPICS_DIR)/topic_service_messages_v1.proto + mkdir ./proto/v1 ./proto/topics || true + $(TOOLS_BIN)/protoc --proto_path=./etc/proto-v1 --go_out=./proto/v1 --go_opt=paths=source_relative --go-grpc_out=./proto/v1 --go-grpc_opt=paths=source_relative etc/proto-v1/proxy_service_messages_v1.proto etc/proto-v1/proxy_service_v1.proto etc/proto-v1/common_messages_v1.proto etc/proto-v1/cache_service_messages_v1.proto etc/proto-v1/queue_service_messages_v1.proto + $(TOOLS_BIN)/protoc --proto_path=./etc/topics --proto_path=./etc/proto-v1 --go_out=./proto/topics --go_opt=paths=source_relative --go-grpc_out=./proto/topics --go-grpc_opt=paths=source_relative etc/topics/topic_service_messages_v1.proto + cat proto/topics/topic_service_messages_v1.pb.go | sed 's,^\tv1 "github.com/oracle/coherence-go-client/proto/v1",\tv1 "github.com/oracle/coherence-go-client/v2/proto/v1",' > /tmp/proto-queues + mv /tmp/proto-queues proto/topics/topic_service_messages_v1.pb.go endif # ---------------------------------------------------------------------------------------------------------------------- @@ -434,6 +444,15 @@ test-e2e-standalone-queues: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e -- $(GO_TEST_FLAGS) -v -coverprofile=$(COVERAGE_DIR)/cover-functional-queues.out -v ./e2e/queues/... -coverpkg=github.com/oracle/coherence-go-client/v2/coherence/... go tool cover -func=$(COVERAGE_DIR)/cover-functional-queues.out | grep -v '0.0%' +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the Go end to end tests for standalone Coherence with Topics +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-e2e-standalone-topics +test-e2e-standalone-topics: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e tests with Coherence queues + cd test && CGO_ENABLED=0 $(GOTESTSUM) --format testname --junitfile $(TEST_LOGS_DIR)/go-client-test-queues.xml \ + -- $(GO_TEST_FLAGS) -v -coverprofile=$(COVERAGE_DIR)/cover-functional-topics.out -v ./e2e/topics/... -coverpkg=github.com/oracle/coherence-go-client/v2/coherence/... + go tool cover -func=$(COVERAGE_DIR)/cover-functional-topics.out | grep -v '0.0%' + # ---------------------------------------------------------------------------------------------------------------------- # Executes the Go end to end tests for gRPC v1 tests # ---------------------------------------------------------------------------------------------------------------------- @@ -450,6 +469,20 @@ test-v1-base: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e tests with Coh test-examples: test-clean gotestsum $(BUILD_PROPS) ## Run examples tests with Coherence ./scripts/run-test-examples.sh +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the tests for DAPR +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-dapr +test-dapr: gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence + ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) + +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the tests for DAPR TLS +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-dapr-tls +test-dapr-tls: gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence with TLS + ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) true + # ---------------------------------------------------------------------------------------------------------------------- # Startup cluster members via docker compose # ---------------------------------------------------------------------------------------------------------------------- diff --git a/coherence/common.go b/coherence/common.go index 2919a90..94a40f9 100644 --- a/coherence/common.go +++ b/coherence/common.go @@ -198,10 +198,9 @@ func unregisterLifecycleListener[K comparable, V any](baseClient *baseClient[K, // executeAddIndex executes the add index operation against a baseClient. func executeAddIndex[K comparable, V, T, E any](ctx context.Context, bc *baseClient[K, V], extractor extractors.ValueExtractor[T, E], sorted bool, comparator extractors.Comparator[E]) error { var ( - extractorSerializer = NewSerializer[any](bc.format) - binExtractor []byte - binComparator []byte - err = bc.ensureClientConnection() + binExtractor []byte + binComparator []byte + err = bc.ensureClientConnection() ) if err != nil { @@ -213,12 +212,12 @@ func executeAddIndex[K comparable, V, T, E any](ctx context.Context, bc *baseCli defer cancel() } - binExtractor, err = extractorSerializer.Serialize(extractor) + binExtractor, err = bc.session.genericSerializer.Serialize(extractor) if err != nil { return err } - binComparator, err = extractorSerializer.Serialize(comparator) + binComparator, err = bc.session.genericSerializer.Serialize(comparator) if err != nil { return err } @@ -236,9 +235,8 @@ func executeAddIndex[K comparable, V, T, E any](ctx context.Context, bc *baseCli // executeRemoveIndex executes the remove index operation against a baseClient. func executeRemoveIndex[K comparable, V, T, E any](ctx context.Context, bc *baseClient[K, V], extractor extractors.ValueExtractor[T, E]) error { var ( - extractorSerializer = NewSerializer[any](bc.format) - binExtractor []byte - err = bc.ensureClientConnection() + binExtractor []byte + err = bc.ensureClientConnection() ) if err != nil { @@ -250,7 +248,7 @@ func executeRemoveIndex[K comparable, V, T, E any](ctx context.Context, bc *base defer cancel() } - binExtractor, err = extractorSerializer.Serialize(extractor) + binExtractor, err = bc.session.genericSerializer.Serialize(extractor) if err != nil { return err } @@ -1016,8 +1014,7 @@ func executeAggregate[K comparable, V, R any](ctx context.Context, bc *baseClien defer cancel() } - aggregatorSerializer := NewSerializer[any](bc.format) - binAggregator, err = aggregatorSerializer.Serialize(aggr) + binAggregator, err = bc.session.genericSerializer.Serialize(aggr) if err != nil { return zeroValue, err } @@ -1034,7 +1031,7 @@ func executeAggregate[K comparable, V, R any](ctx context.Context, bc *baseClien } } else if filter != nil { // filter was specified - binFilter, err = NewSerializer[any](bc.format).Serialize(filter) + binFilter, err = bc.session.genericSerializer.Serialize(filter) if err != nil { return zeroValue, err } @@ -1112,8 +1109,7 @@ func executeInvoke[K comparable, V any, R any](ctx context.Context, bc *baseClie return zeroValue, err } - procSerializer := NewSerializer[any](bc.format) - binProcessor, err = procSerializer.Serialize(proc) + binProcessor, err = bc.session.genericSerializer.Serialize(proc) if err != nil { return zeroValue, err } @@ -1174,14 +1170,13 @@ func executeInvokeAllFilterOrKeys[K comparable, V any, R any](ctx context.Contex newCtx, cancel := bc.session.ensureContext(ctx) - procSerializer := NewSerializer[any](bc.format) - if binProcessor, err = procSerializer.Serialize(proc); err != nil { + if binProcessor, err = bc.session.genericSerializer.Serialize(proc); err != nil { ch <- &StreamedEntry[K, R]{Err: err} return ch } if fltr != nil { - if binFilter, err = NewSerializer[any](bc.format).Serialize(fltr); err != nil { + if binFilter, err = bc.session.genericSerializer.Serialize(fltr); err != nil { ch <- &StreamedEntry[K, R]{Err: err} return ch } @@ -1326,7 +1321,7 @@ func executeKeySetFilter[K comparable, V any](ctx context.Context, bc *baseClien if fltr == nil { fltr = filters.Always() } - binFilter, err = NewSerializer[any](bc.format).Serialize(fltr) + binFilter, err = bc.session.genericSerializer.Serialize(fltr) if err != nil { ch <- &StreamedKey[K]{Err: err} return ch @@ -1906,7 +1901,6 @@ func executeEntrySetFilter[K comparable, V any, E any](ctx context.Context, bc * binFilter = make([]byte, 0) binComparator = make([]byte, 0) ch = make(chan *StreamedEntry[K, V]) - serializer = NewSerializer[any](bc.format) ) if err != nil { @@ -1919,14 +1913,14 @@ func executeEntrySetFilter[K comparable, V any, E any](ctx context.Context, bc * if fltr == nil { fltr = filters.Always() } - binFilter, err = serializer.Serialize(fltr) + binFilter, err = bc.session.genericSerializer.Serialize(fltr) if err != nil { ch <- &StreamedEntry[K, V]{Err: err} return ch } if comparator != nil { - binComparator, err = serializer.Serialize(comparator) + binComparator, err = bc.session.genericSerializer.Serialize(comparator) if err != nil { ch <- &StreamedEntry[K, V]{Err: err} return ch @@ -2008,7 +2002,6 @@ func executeValues[K comparable, V any, E any](ctx context.Context, bc *baseClie binFilter = make([]byte, 0) binComparator = make([]byte, 0) ch = make(chan *StreamedValue[V]) - serializer = NewSerializer[any](bc.format) ) if err != nil { @@ -2021,14 +2014,14 @@ func executeValues[K comparable, V any, E any](ctx context.Context, bc *baseClie if fltr == nil { fltr = filters.Always() } - binFilter, err = serializer.Serialize(fltr) + binFilter, err = bc.session.genericSerializer.Serialize(fltr) if err != nil { ch <- &StreamedValue[V]{Err: err} return ch } if comparator != nil { - binComparator, err = serializer.Serialize(comparator) + binComparator, err = bc.session.genericSerializer.Serialize(comparator) if err != nil { ch <- &StreamedValue[V]{Err: err} return ch diff --git a/coherence/named_map_client.go b/coherence/named_map_client.go index 53286ba..802e927 100644 --- a/coherence/named_map_client.go +++ b/coherence/named_map_client.go @@ -35,7 +35,7 @@ func (nm *NamedMapClient[K, V]) getBaseClient() *baseClient[K, V] { //nolint // Invoke the specified processor against the entry mapped to the specified key. // Processors are invoked atomically against a specific entry as the process may mutate the entry. // The type parameter is R = type of the result of the invocation. -// +// NamedMapClient // The example below shows how to run an entry processor to increment the age of person identified by the key 1. // // namedMap, err := coherence.GetNamedMap[int, Person](session, "people") diff --git a/coherence/publisher/doc.go b/coherence/publisher/doc.go new file mode 100644 index 0000000..ad61ca6 --- /dev/null +++ b/coherence/publisher/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 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 publisher provides various publisher functions and types. +*/ +package publisher diff --git a/coherence/publisher/publisher.go b/coherence/publisher/publisher.go new file mode 100644 index 0000000..69a0c48 --- /dev/null +++ b/coherence/publisher/publisher.go @@ -0,0 +1,111 @@ +/* + * Copyright (c) 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 publisher + +import ( + "fmt" + "math" + "math/rand/v2" + "sync/atomic" +) + +var ( + _ OrderingOption = &OrderByDefault{} + _ OrderingOption = &OrderByRoundRobin{} + //_ OrderingOption = &OrderByValue{} +) + +// PublishStatus provides the result of a publish operation. +type PublishStatus struct { + PublishedChannel int32 + RemainingCapacity int32 +} + +// Options provides options for creating a publisher. +type Options struct { + ChannelCount int32 + Ordering OrderingOption +} + +func (o Options) String() string { + return fmt.Sprintf("options{ChannelCount:%d, ordering=%v}", o.ChannelCount, o.Ordering) +} + +// EnsurePublisherResult contains the result of an ensure publisher request. +type EnsurePublisherResult struct { + ProxyID int32 + PublisherID int64 + ChannelCount int32 +} + +func (o Options) GetChannelCount() int32 { + return o.ChannelCount +} + +func (o Options) GetOrdering() OrderingOption { + return o.Ordering +} + +// WithChannelCount returns a function to set the channels for a [Publisher]. +func WithChannelCount(channelCount int32) func(options *Options) { + return func(t *Options) { + t.ChannelCount = channelCount + } +} + +// WithDefaultOrdering returns a function to set the ordering for a [Publisher]. +func WithDefaultOrdering() func(options *Options) { + return func(t *Options) { + t.Ordering = &OrderByDefault{} + } +} + +// WithRoundRobinOrdering returns a function to set the ordering to round robin for a [Publisher]. +func WithRoundRobinOrdering() func(options *Options) { + return func(t *Options) { + t.Ordering = &OrderByRoundRobin{} + } +} + +// OrderingOption defines the type of publisher ordering. +type OrderingOption interface { + GetPublishHash() int32 +} + +// OrderByDefault defines default ordering. +type OrderByDefault struct { +} + +func (o *OrderByDefault) GetPublishHash() int32 { + // #nosec G404 -- math/rand is fine here for non-security use + return rand.Int32() +} + +func (o *OrderByDefault) String() string { + return "default" +} + +// OrderByRoundRobin defines default ordering. +type OrderByRoundRobin struct { + counter int64 +} + +func (o *OrderByRoundRobin) String() string { + return "roundRobin" +} + +func (o *OrderByRoundRobin) GetPublishHash() int32 { + newVal := atomic.AddInt64(&o.counter, 1) + + if newVal >= int64(math.MaxInt32) { + atomic.CompareAndSwapInt64(&o.counter, newVal, 0) + return 0 + } + + // #nosec G115 -- val is guaranteed to be in int32 range + return int32(newVal) +} diff --git a/coherence/publisher/publisher_test.go b/coherence/publisher/publisher_test.go new file mode 100644 index 0000000..7cbc0bd --- /dev/null +++ b/coherence/publisher/publisher_test.go @@ -0,0 +1,37 @@ +/* + * Copyright (c) 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 publisher + +import "testing" + +func TestRoundRobinOrdering(t *testing.T) { + roundRobin := &OrderByRoundRobin{} + var ( + i int32 + ) + + for i = 1; i <= 10; i++ { + next := roundRobin.GetPublishHash() + if next != i { + t.Fatalf("expected next=%d, got %d", i, next) + } + } +} + +func TestDefaultOrdering(t *testing.T) { + defaultOrdering := &OrderByDefault{} + var ( + i int32 + ) + + for i = 1; i <= 10; i++ { + next := defaultOrdering.GetPublishHash() + if next < 0 { + t.Fatalf("expected next=%d, got %d", i, next) + } + } +} diff --git a/coherence/publisher_events.go b/coherence/publisher_events.go new file mode 100644 index 0000000..5e833fa --- /dev/null +++ b/coherence/publisher_events.go @@ -0,0 +1,198 @@ +/* + * Copyright (c) 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 coherence + +import ( + "fmt" +) + +const ( + PublisherConnected PublisherLifecycleEventType = "publisher_connected" + PublisherDisconnected PublisherLifecycleEventType = "publisher_disconnected" + PublisherReleased PublisherLifecycleEventType = "publisher_released" + PublisherDestroyed PublisherLifecycleEventType = "publisher_destroyed" + PublisherChannelsFreed PublisherLifecycleEventType = "publisher_channels_freed" +) + +type PublisherLifecycleEventType string + +type PublisherLifecycleEvent[V any] interface { + Source() Publisher[V] + Type() PublisherLifecycleEventType +} + +type publisherLifecycleEvent[V any] struct { + // source the source of the event + source Publisher[V] + + // Type of this event's PublisherLifecycleEventType + eventType PublisherLifecycleEventType +} + +// Type returns the [PublisherLifecycleEventType] for this [PublisherLifecycleEvent]. +func (l *publisherLifecycleEvent[V]) Type() PublisherLifecycleEventType { + return l.eventType +} + +// Source returns the source of this [PublisherLifecycleEvent]. +func (l *publisherLifecycleEvent[V]) Source() Publisher[V] { + return l.source +} + +// String returns a string representation of a [PublisherLifecycleEvent].. +func (l *publisherLifecycleEvent[V]) String() string { + return fmt.Sprintf("spublisherLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) +} + +// PublisherLifecycleListener allows registering callbacks to be notified when lifecycle events +// occur against a [Publisher]. +type PublisherLifecycleListener[V any] interface { + // OnAny registers a callback that will be notified when any [Publisher] event occurs. + OnAny(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnDestroyed registers a callback that will be notified when a [Publisher] is destroyed. + OnDestroyed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnReleased registers a callback that will be notified when a [Publisher] is released. + OnReleased(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnConnected registers a callback that will be notified when a [Publisher] is disconnected. + OnConnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnDisconnected registers a callback that will be notified when a [Publisher] is unsubscribed. + OnDisconnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnChannelsFreed registers a callback that will be notified when a [Publisher] has channels freed. + OnChannelsFreed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + getEmitter() *eventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]] +} + +// NewPublisherLifecycleListener creates and returns a pointer to a new [PublisherLifecycleListener] instance. +func NewPublisherLifecycleListener[V any]() PublisherLifecycleListener[V] { + return &publisherLifecycleListener[V]{newEventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]]()} +} + +type publisherLifecycleListener[V any] struct { //lint:ignore U1000 - required due to linter issues with generics + emitter *eventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]] +} + +// OnAny registers a callback that will be notified when any [Publisher] event occurs. +func (t *publisherLifecycleListener[V]) OnAny(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.OnDestroyed(callback).OnReleased(callback).OnDisconnected(callback). + OnDisconnected(callback).OnChannelsFreed(callback) +} + +// OnDestroyed registers a callback that will be notified when a [SubscPublisherriber] is destroyed. +func (t *publisherLifecycleListener[V]) OnDestroyed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherDestroyed, callback) +} + +// OnReleased registers a callback that will be notified when a [Publisher] is released. +func (t *publisherLifecycleListener[V]) OnReleased(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherReleased, callback) +} + +// OnDisconnected registers a callback that will be notified when a [Publisher] is disconnected. +func (t *publisherLifecycleListener[V]) OnDisconnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherDisconnected, callback) +} + +// OnConnected registers a callback that will be notified when a [Publisher] is connected. +func (t *publisherLifecycleListener[V]) OnConnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherConnected, callback) +} + +// OnChannelsFreed registers a callback that will be notified when a [Publisher] has channels freed. +func (t *publisherLifecycleListener[V]) OnChannelsFreed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherChannelsFreed, callback) +} + +func (t *publisherLifecycleListener[V]) getEmitter() *eventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]] { + return t.emitter +} + +// on registers a callback for the specified [PublisherLifecycleEventType]. +func (t *publisherLifecycleListener[V]) on(event PublisherLifecycleEventType, callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + t.emitter.on(event, callback) + return t +} + +func (tp *topicPublisher[V]) AddLifecycleListener(listener PublisherLifecycleListener[V]) error { + if tp.isClosed { + return ErrPublisherClosed + } + + tp.addLifecycleListener(listener) + return nil +} + +func (tp *topicPublisher[V]) RemoveLifecycleListener(listener PublisherLifecycleListener[V]) error { + if tp.isClosed { + return ErrPublisherClosed + } + + tp.removeLifecycleListener(listener) + return nil +} + +// addLifecycleListener adds the specified [PublisherLifecycleListener]. +func (tp *topicPublisher[V]) addLifecycleListener(listener PublisherLifecycleListener[V]) { + tp.mutex.Lock() + defer tp.mutex.Unlock() + + for _, e := range tp.lifecycleListenersV1 { + if *e == listener { + return + } + } + tp.lifecycleListenersV1 = append(tp.lifecycleListenersV1, &listener) +} + +// removeLifecycleListener removes the specified [TopicLifecycleListener]. +func (tp *topicPublisher[V]) removeLifecycleListener(listener PublisherLifecycleListener[V]) { + tp.mutex.Lock() + defer tp.mutex.Unlock() + + idx := -1 + listeners := tp.lifecycleListenersV1 + for i, c := range listeners { + if *c == listener { + idx = i + break + } + } + if idx != -1 { + result := append(listeners[:idx], listeners[idx+1:]...) + tp.lifecycleListenersV1 = result + } +} + +func newPublisherLifecycleEvent[V any](nt Publisher[V], eventType PublisherLifecycleEventType) PublisherLifecycleEvent[V] { + return &publisherLifecycleEvent[V]{source: nt, eventType: eventType} +} + +// generatePublisherLifecycleEvent emits the publisher lifecycle events. +func (tp *topicPublisher[V]) generatePublisherLifecycleEvent(client interface{}, eventType PublisherLifecycleEventType) { + listeners := tp.lifecycleListenersV1 + + if pub, ok := client.(Publisher[V]); ok || client == nil { + event := newPublisherLifecycleEvent[V](pub, eventType) + for _, l := range listeners { + e := *l + e.getEmitter().emit(eventType, event) + } + // TODO: + //if eventType == TopicDestroyed { + // _ = releaseTopicInternal[V](context.Background(), bt, false) + //} + } +} + +type PublisherEventSubmitter interface { + generatePublisherLifecycleEvent(client interface{}, eventType PublisherLifecycleEventType) +} diff --git a/coherence/queue_events.go b/coherence/queue_events.go index 1b0284b..7310357 100644 --- a/coherence/queue_events.go +++ b/coherence/queue_events.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 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,7 +15,7 @@ const ( // QueueTruncated raised when a queue is truncated. QueueTruncated QueueLifecycleEventType = "queue_truncated" - // QueueReleased raised when a queue is released but the session. + // QueueReleased raised when a queue is released by the session. QueueReleased QueueLifecycleEventType = "queue_released" ) diff --git a/coherence/session.go b/coherence/session.go index 3378d1c..8b45e90 100644 --- a/coherence/session.go +++ b/coherence/session.go @@ -59,12 +59,19 @@ type Session struct { conn *grpc.ClientConn dialOptions []grpc.DialOption closed bool + genericSerializer Serializer[any] mapMutex sync.RWMutex caches map[string]interface{} maps map[string]interface{} queues map[string]interface{} + topics map[string]interface{} + publishers map[int64]interface{} + subscribers map[int64]interface{} cacheIDMap safeMap[string, int32] queueIDMap safeMap[string, int32] + topicIDMap safeMap[string, int32] + subscriberIDMap safeMap[int64, int32] + publisherIDMap safeMap[int64, int32] lifecycleMutex sync.RWMutex lifecycleListeners []*SessionLifecycleListener sessionConnectCtx context.Context @@ -77,6 +84,7 @@ type Session struct { filterID int64 // filter id for gRPC v1 v1StreamManagerCache *streamManagerV1 v1StreamManagerQueue *streamManagerV1 + v1StreamManagerTopics *streamManagerV1 } // SessionOptions holds the session attributes like host, port, tls attributes etc. @@ -171,8 +179,14 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( maps: make(map[string]interface{}, 0), caches: make(map[string]interface{}, 0), queues: make(map[string]interface{}, 0), + topics: make(map[string]interface{}, 0), + publishers: make(map[int64]interface{}, 0), + subscribers: make(map[int64]interface{}, 0), cacheIDMap: newSafeIDMap(), queueIDMap: newSafeIDMap(), + topicIDMap: newSafeIDMap(), + publisherIDMap: newSafeIDMapInt64(), + subscriberIDMap: newSafeIDMapInt64(), lifecycleListeners: []*SessionLifecycleListener{}, sessOpts: &SessionOptions{ PlainText: false, @@ -221,6 +235,8 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( return nil, ErrInvalidFormat } + session.genericSerializer = NewSerializer[any](session.sessOpts.Format) + // if no address option sent in then use the env or defaults if session.sessOpts.Address == "" { session.sessOpts.Address = getStringValueFromEnvVarOrDefault(envHostName, "localhost:1408") @@ -402,6 +418,10 @@ func (s *Session) getQueueID(queue string) *int32 { return s.queueIDMap.Get(queue) } +func (s *Session) getTopicID(topic string) *int32 { + return s.topicIDMap.Get(topic) +} + func (s *Session) getCacheNameFromCacheID(cacheID int32) *string { return s.cacheIDMap.KeyFromValue(cacheID) } @@ -1001,3 +1021,7 @@ func (m *safeMapImpl[K, V]) Clear() { func newSafeIDMap() safeMap[string, int32] { return &safeMapImpl[string, int32]{internalMap: make(map[string]int32, 0)} } + +func newSafeIDMapInt64() safeMap[int64, int32] { + return &safeMapImpl[int64, int32]{internalMap: make(map[int64]int32, 0)} +} diff --git a/coherence/subscriber/doc.go b/coherence/subscriber/doc.go new file mode 100644 index 0000000..ff6ccc4 --- /dev/null +++ b/coherence/subscriber/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 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 subscriber provides various subscriber functions and types. +*/ +package subscriber diff --git a/coherence/subscriber/subscriber.go b/coherence/subscriber/subscriber.go new file mode 100644 index 0000000..765e768 --- /dev/null +++ b/coherence/subscriber/subscriber.go @@ -0,0 +1,73 @@ +/* + * Copyright (c) 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 subscriber + +import ( + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/filters" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" +) + +type ReceiveStatus int32 + +const ( + ReceiveSuccess = pb1topics.ReceiveStatus_ReceiveSuccess + ChannelExhausted = pb1topics.ReceiveStatus_ChannelExhausted + ChannelNotAllocatedChannel = pb1topics.ReceiveStatus_ChannelNotAllocatedChannel + UnknownSubscriber = pb1topics.ReceiveStatus_UnknownSubscriber +) + +// EnsureSubscriberResult contains the result of an ensure subscriber request. +type EnsureSubscriberResult struct { + ProxyID int32 + SubscriberID int64 + SubscriberGroupID int64 + UUID string +} + +type ReceiveResult[V any] struct { + Status ReceiveStatus +} + +// Options provides options for creating a subscriber. +type Options struct { + SubscriberGroup *string + Filter filters.Filter + Extractor []byte +} + +// TODO: Additional options +//// the optional name of the subscriber group +//SubscriberGroup *string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3,oneof" json:"subscriberGroup,omitempty"` +//// an optional ValueExtractor to convert received messages +//Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` +//// True to return an empty value if the topic is empty + +// InSubscriberGroup returns a function to set the subscriber group for a [Subscriber]. +func InSubscriberGroup(group string) func(options *Options) { + return func(s *Options) { + s.SubscriberGroup = &group + } +} + +// WithFilter returns a function to set the [filters.Filter] for a [Subscriber]. +func WithFilter(fltr filters.Filter) func(options *Options) { + return func(s *Options) { + s.Filter = fltr + } +} + +// WithTransformer returns a function to set the extractor [Subscriber]. +func WithTransformer(extractor []byte) func(options *Options) { + return func(s *Options) { + s.Extractor = extractor + } +} + +func (o *Options) String() string { + return fmt.Sprintf("options{SubscriberGroup=%v, filter=%v}", o.SubscriberGroup, o.Filter) +} diff --git a/coherence/subscriber_events.go b/coherence/subscriber_events.go new file mode 100644 index 0000000..0ce7870 --- /dev/null +++ b/coherence/subscriber_events.go @@ -0,0 +1,236 @@ +/* + * Copyright (c) 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 coherence + +import ( + "fmt" +) + +const ( + // SubscriberDisconnected raised when a subscriber] is disconnected. + SubscriberDisconnected SubscriberLifecycleEventType = "subscriber_disconnected" + SubscriberReleased SubscriberLifecycleEventType = "subscriber_released" + SubscriberDestroyed SubscriberLifecycleEventType = "subscriber_destroyed" + SubscriberUnsubscribed SubscriberLifecycleEventType = "subscriber_unsubscribed" + SubscriberChannelHead SubscriberLifecycleEventType = "subscriber_channel_head" + SubscriberChannelPopulated SubscriberLifecycleEventType = "subscriber_channel_populated" + SubscriberChannelsLost SubscriberLifecycleEventType = "subscriber_channel_lost" + SubscriberChannelAllocation SubscriberLifecycleEventType = "subscriber_channel_allocation" + SubscriberGroupDestroyed SubscriberLifecycleEventType = "subscriber_group_destroyed" +) + +type SubscriberLifecycleEventType string + +type SubscriberLifecycleEvent[V any] interface { + Source() Subscriber[V] + Type() SubscriberLifecycleEventType +} + +type subscriberLifecycleEvent[V any] struct { + // source the source of the event + source Subscriber[V] + + // Type of this event's SubscriberLifecycleEventType + eventType SubscriberLifecycleEventType +} + +// Type returns the [SubscriberLifecycleEventType] for this [SubscriberLifecycleEvent]. +func (l *subscriberLifecycleEvent[V]) Type() SubscriberLifecycleEventType { + return l.eventType +} + +// Source returns the source of this [SubscriberLifecycleEvent]. +func (l *subscriberLifecycleEvent[V]) Source() Subscriber[V] { + return l.source +} + +// String returns a string representation of a [SubscriberLifecycleEvent].\. +func (l *subscriberLifecycleEvent[V]) String() string { + return fmt.Sprintf("subscriberLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) +} + +// SubscriberLifecycleListener allows registering callbacks to be notified when lifecycle events +// occur against a [Subscriber]. +type SubscriberLifecycleListener[V any] interface { + // OnAny registers a callback that will be notified when any [Subscriber] event occurs. + OnAny(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnDestroyed registers a callback that will be notified when a [Subscriber] is destroyed. + OnDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnReleased registers a callback that will be notified when a [Subscriber] is released. + OnReleased(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnDisconnected registers a callback that will be notified when a [Subscriber] is disconnected. + OnDisconnected(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnUnsubscribed registers a callback that will be notified when a [Subscriber] is unsubscribed. + OnUnsubscribed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelsLost registers a callback that will be notified when a [Subscriber] loses channels. + OnChannelsLost(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelHeadChanged registers a callback that will be notified when head position of a channel has changed for a [Subscriber]. + OnChannelHeadChanged(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelPopulated registers a callback that will be notified when a channel is populated for a [Subscriber]. + OnChannelPopulated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelAllocated registers a callback that will be notified when a channel is allocated for a [Subscriber]. + OnChannelAllocated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnSubscriberGroupDestroyed registers a callback that will be notified when a subscriber group is destroyed for a [Subscriber]. + OnSubscriberGroupDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + getEmitter() *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] +} + +// NewSubscriberLifecycleListener creates and returns a pointer to a new [SubscriberLifecycleListener] instance. +func NewSubscriberLifecycleListener[V any]() SubscriberLifecycleListener[V] { + return &subscriberLifecycleListener[V]{newEventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]]()} +} + +type subscriberLifecycleListener[V any] struct { //lint:ignore U1000 - required due to linter issues with generics + emitter *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] +} + +// OnAny registers a callback that will be notified when any [Subscriber] event occurs. +func (t *subscriberLifecycleListener[V]) OnAny(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.OnDestroyed(callback).OnReleased(callback).OnDisconnected(callback). + OnUnsubscribed(callback).OnChannelsLost(callback).OnChannelHeadChanged(callback). + OnChannelPopulated(callback).OnSubscriberGroupDestroyed(callback).OnChannelAllocated(callback) +} + +// OnDestroyed registers a callback that will be notified when a [Subscriber] is destroyed. +func (t *subscriberLifecycleListener[V]) OnDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberDestroyed, callback) +} + +// OnReleased registers a callback that will be notified when a [Subscriber] is released. +func (t *subscriberLifecycleListener[V]) OnReleased(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberReleased, callback) +} + +// OnDisconnected registers a callback that will be notified when a [Subscriber] is disconnected. +func (t *subscriberLifecycleListener[V]) OnDisconnected(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberDisconnected, callback) +} + +// OnUnsubscribed registers a callback that will be notified when a [Subscriber] is unsubscribed. +func (t *subscriberLifecycleListener[V]) OnUnsubscribed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberUnsubscribed, callback) +} + +// OnChannelsLost registers a callback that will be notified when a [Subscriber] loses channels. +func (t *subscriberLifecycleListener[V]) OnChannelsLost(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelsLost, callback) +} + +// OnChannelHeadChanged registers a callback that will be notified when head position of a channel has changed for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnChannelHeadChanged(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelHead, callback) +} + +// OnChannelPopulated registers a callback that will be notified when a channel is populated for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnChannelPopulated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelPopulated, callback) +} + +// OnChannelAllocated registers a callback that will be notified when a channel is allocated for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnChannelAllocated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelAllocation, callback) +} + +// OnSubscriberGroupDestroyed registers a callback that will be notified when a subscriber group is destroyed for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnSubscriberGroupDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberGroupDestroyed, callback) +} + +func (t *subscriberLifecycleListener[V]) getEmitter() *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] { + return t.emitter +} + +// on registers a callback for the specified [TopicLifecycleEventType]. +func (t *subscriberLifecycleListener[V]) on(event SubscriberLifecycleEventType, callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + t.emitter.on(event, callback) + return t +} + +func (ts *topicSubscriber[V]) AddLifecycleListener(listener SubscriberLifecycleListener[V]) error { + if ts.isClosed { + return ErrSubscriberClosed + } + + ts.addLifecycleListener(listener) + return nil +} + +func (ts *topicSubscriber[V]) RemoveLifecycleListener(listener SubscriberLifecycleListener[V]) error { + if ts.isClosed { + return ErrSubscriberClosed + } + + ts.removeLifecycleListener(listener) + return nil +} + +// addLifecycleListener adds the specified [SubscriberLifecycleListener]. +func (ts *topicSubscriber[V]) addLifecycleListener(listener SubscriberLifecycleListener[V]) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + for _, e := range ts.lifecycleListenersV1 { + if *e == listener { + return + } + } + ts.lifecycleListenersV1 = append(ts.lifecycleListenersV1, &listener) +} + +// removeLifecycleListener removes the specified [TopicLifecycleListener]. +func (ts *topicSubscriber[V]) removeLifecycleListener(listener SubscriberLifecycleListener[V]) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + idx := -1 + listeners := ts.lifecycleListenersV1 + for i, c := range listeners { + if *c == listener { + idx = i + break + } + } + if idx != -1 { + result := append(listeners[:idx], listeners[idx+1:]...) + ts.lifecycleListenersV1 = result + } +} + +func newSubscriberLifecycleEvent[V any](nt Subscriber[V], eventType SubscriberLifecycleEventType) SubscriberLifecycleEvent[V] { + return &subscriberLifecycleEvent[V]{source: nt, eventType: eventType} +} + +// generateSubscriberLifecycleEvent emits the subscriber lifecycle events. +func (ts *topicSubscriber[V]) generateSubscriberLifecycleEvent(client interface{}, eventType SubscriberLifecycleEventType) { + listeners := ts.lifecycleListenersV1 + + if sub, ok := client.(Subscriber[V]); ok || client == nil { + event := newSubscriberLifecycleEvent[V](sub, eventType) + for _, l := range listeners { + e := *l + e.getEmitter().emit(eventType, event) + } + // TODO: + //if eventType == TopicDestroyed { + // _ = releaseTopicInternal[V](context.Background(), bt, false) + //} + } +} + +type SubscriberEventSubmitter interface { + generateSubscriberLifecycleEvent(client interface{}, eventType SubscriberLifecycleEventType) +} diff --git a/coherence/subscribergroup/doc.go b/coherence/subscribergroup/doc.go new file mode 100644 index 0000000..9aeb895 --- /dev/null +++ b/coherence/subscribergroup/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 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 subscribergroup provides various subscriber group functions and types. +*/ +package subscribergroup diff --git a/coherence/subscribergroup/subscriber.go b/coherence/subscribergroup/subscriber.go new file mode 100644 index 0000000..266b605 --- /dev/null +++ b/coherence/subscribergroup/subscriber.go @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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 subscribergroup + +import ( + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/filters" +) + +// Options provides options for creating a subscriber. +type Options struct { + Filter filters.Filter + Extractor []byte +} + +// WithFilter returns a function to set the [filters.Filter] for a [Subscriber]. +func WithFilter(fltr filters.Filter) func(options *Options) { + return func(s *Options) { + s.Filter = fltr + } +} + +// WithTransformer returns a function to set the extractor [Subscriber]. +func WithTransformer(extractor []byte) func(options *Options) { + return func(s *Options) { + s.Extractor = extractor + } +} + +func (o *Options) String() string { + return fmt.Sprintf("options{filter=%v}", o.Filter) +} diff --git a/coherence/topic/doc.go b/coherence/topic/doc.go new file mode 100644 index 0000000..e51b646 --- /dev/null +++ b/coherence/topic/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 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 topic provides various topic and functions and types. +*/ +package topic diff --git a/coherence/topic/topic.go b/coherence/topic/topic.go new file mode 100644 index 0000000..c5cc516 --- /dev/null +++ b/coherence/topic/topic.go @@ -0,0 +1,25 @@ +/* + * Copyright (c) 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 topic + +import "fmt" + +// Options provides options for creating topic. +type Options struct { + ChannelCount int32 +} + +// WithChannelCount returns a function to set the default channel count on a [NamedTopic]. +func WithChannelCount(channelCount int32) func(cacheOptions *Options) { + return func(t *Options) { + t.ChannelCount = channelCount + } +} + +func (o *Options) String() string { + return fmt.Sprintf("options{ChannelCount=%d}", o.ChannelCount) +} diff --git a/coherence/topics.go b/coherence/topics.go new file mode 100644 index 0000000..fdb2a00 --- /dev/null +++ b/coherence/topics.go @@ -0,0 +1,1067 @@ +/* + * Copyright (c) 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 coherence + +import ( + "context" + "errors" + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/extractors" + "github.com/oracle/coherence-go-client/v2/coherence/publisher" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" + "github.com/oracle/coherence-go-client/v2/coherence/subscribergroup" + "github.com/oracle/coherence-go-client/v2/coherence/topic" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" + pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/wrapperspb" + "strings" + "sync" +) + +const ( + defaultChannelCount = 17 +) + +var ( + _ Publisher[string] = &topicPublisher[string]{} + _ NamedTopic[string] = &baseTopicsClient[string]{} + _ Subscriber[string] = &topicSubscriber[string]{} + + ErrTopicDestroyedOrReleased = errors.New("this topic has been destroyed or released") + ErrTopicsNoSupported = errors.New("the coherence server version must support protocol version 1 or above to use topic") + ErrTopicFull = errors.New("unable to publish, this topic is full") + ErrPublisherClosed = errors.New("publisher has been closed and is no longer usable") + ErrSubscriberClosed = errors.New("subscriber has been closed and is no longer usable") +) + +// NamedTopic defines the APIs to interact with Coherence topic allowing to publish and +// subscribe from Go client. +// +// The type parameter is V = type of the value. +type NamedTopic[V any] interface { + // Destroy destroys this topic on the server and releases all resources. After this operation it is no longer usable on the client or server. + Destroy(ctx context.Context) error + + // Close closes a topic and releases the resources associated with it on the client only. + Close(ctx context.Context) error + + // CreatePublisher creates a Publisher with the specified options. + CreatePublisher(ctx context.Context, options ...func(o *publisher.Options)) (Publisher[V], error) + + // CreateSubscriber creates a Subscriber with the specified options. + // Note: If you wish to create a Subscriber with a transformer, you should use the helper + // function CreatSubscriberWithTransformer. + CreateSubscriber(ctx context.Context, options ...func(o *subscriber.Options)) (Subscriber[V], error) + + // CreateSubscriberGroup creates a subscriber group with the given options. + CreateSubscriberGroup(ctx context.Context, subscriberGroup string, options ...func(o *subscribergroup.Options)) error + + // DestroySubscriberGroup destroys a subscriber group. + // TODO: Not viable to implement? + DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error + + // AddLifecycleListener adds a [TopicLifecycleListener] to this topic. + AddLifecycleListener(listener TopicLifecycleListener[V]) error + + // RemoveLifecycleListener removes a [TopicLifecycleListener] from this topic. + RemoveLifecycleListener(listener TopicLifecycleListener[V]) error + + // GetName returns the name of this topic. + GetName() string +} + +// Publisher provides the means to publish messages to a [NamedTopic]. +type Publisher[V any] interface { + GetProxyID() int32 + GetPublisherID() int64 + GetChannelCount() int32 + + // Publish publishes a message and returns a status. + Publish(ctx context.Context, value V) (*publisher.PublishStatus, error) + + // Close closes a publisher and releases all resources associated with it in the client + // and on the server. + Close(ctx context.Context) error + + // AddLifecycleListener adds a [PublisherLifecycleListener] to this topic. + AddLifecycleListener(listener PublisherLifecycleListener[V]) error + + // RemoveLifecycleListener removes a [PublisherLifecycleListener] from this topic. + RemoveLifecycleListener(listener PublisherLifecycleListener[V]) error +} + +// Subscriber subscribes directly to a [NamedTopic], or to a subscriber group of a [NamedTopic]. +type Subscriber[V any] interface { + // Close closes a subscriber and releases all resources associated with it in the client + // and on the server. + Close(ctx context.Context) error + + // DestroySubscriberGroup destroys a subscriber group created by this subscriber. + DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error + + // AddLifecycleListener adds a [SubscriberLifecycleListener] to this subscriber. + AddLifecycleListener(listener SubscriberLifecycleListener[V]) error + + // RemoveLifecycleListener removes a [SubscriberLifecycleListener] from this subscriber. + RemoveLifecycleListener(listener SubscriberLifecycleListener[V]) error +} + +// GetNamedTopic gets a [NamedTopic] of the generic type specified or if a topic already exists with the +// same type parameters, it will return it otherwise it will create a new one. +func GetNamedTopic[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *topic.Options)) (NamedTopic[V], error) { + var ( + existingTopic interface{} + ok bool + err error + ) + + topicOptions := &topic.Options{ + ChannelCount: defaultChannelCount, + } + + // apply any cache options + for _, f := range options { + f(topicOptions) + } + + // protect updates to maps + session.mapMutex.Lock() + + // check to see if we already have an entry for the queue + if existingTopic, ok = session.topics[topicName]; ok { + defer session.mapMutex.Unlock() + + existing, ok2 := existingTopic.(NamedTopic[V]) + if !ok2 { + // the casting failed so return an error indicating the queue exists with different type mappings + return nil, getExistingError("NamedTopic", topicName) + } + + // check any topic options + + session.debug("using existing NamedTopic: %v", existing) + return existing, nil + } + + session.topics[topicName] = nil + session.mapMutex.Unlock() + + bt, err := ensureTopicInternal[V](ctx, session, topicName, topicOptions) + if err != nil { + return nil, err + } + + newTopic := &namedTopic[V]{baseTopicsClient: bt} + + session.topics[topicName] = newTopic + + return newTopic, nil +} + +type topicPublisher[V any] struct { + session *Session + isClosed bool + namedTopic *baseTopicsClient[V] // may be nil if created outside topic + topicName string + proxyID int32 + publisherID int64 + channelCount int32 + options *publisher.Options + valueSerializer Serializer[V] + mutex sync.RWMutex + lifecycleListenersV1 []*PublisherLifecycleListener[V] +} + +func (tp *topicPublisher[V]) GetProxyID() int32 { + return tp.proxyID +} + +func (tp *topicPublisher[V]) GetPublisherID() int64 { + return tp.publisherID +} + +func (tp *topicPublisher[V]) GetChannelCount() int32 { + return tp.channelCount +} + +func (tp *topicPublisher[V]) Publish(ctx context.Context, value V) (*publisher.PublishStatus, error) { + if tp.isClosed { + return nil, ErrPublisherClosed + } + + publishChannel := tp.ensureTopicChannel() + + binValue, err := tp.valueSerializer.Serialize(value) + if err != nil { + return nil, err + } + + return tp.session.v1StreamManagerTopics.publishToTopic(ctx, tp.proxyID, publishChannel, binValue) +} + +func (tp *topicPublisher[V]) Close(ctx context.Context) error { + if tp.isClosed { + return ErrPublisherClosed + } + tp.isClosed = true + tp.session.publisherIDMap.Remove(tp.publisherID) + + tp.session.mapMutex.Lock() + delete(tp.session.publishers, tp.publisherID) + + tp.session.mapMutex.Unlock() + tp.generatePublisherLifecycleEvent(tp, PublisherReleased) + + return tp.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, tp.proxyID, pb1topics.TopicServiceRequestType_DestroyPublisher) +} + +func (tp *topicPublisher[V]) String() string { + return fmt.Sprintf("publisher{publisherID=%d, topicName=%s, proxyID=%d, channelCount=%d, %v, destroyed=%v}", + tp.publisherID, tp.topicName, tp.proxyID, tp.channelCount, tp.options, tp.isClosed) +} + +// ensureTopicChannel returns the channel to publish to based upon the [publisher.Options]. +func (tp *topicPublisher[V]) ensureTopicChannel() int32 { + return tp.options.GetOrdering().GetPublishHash() % tp.channelCount +} + +type baseTopicsClient[V any] struct { + session *Session + valueSerializer Serializer[V] + name string + ctx context.Context + topicID int32 + isDestroyed bool + isReleased bool + mutex *sync.RWMutex + topicOpts *topic.Options + lifecycleListenersV1 []*TopicLifecycleListener[V] +} + +type namedTopic[V any] struct { + *baseTopicsClient[V] +} + +func (bt *baseTopicsClient[V]) Destroy(ctx context.Context) error { + return releaseTopicInternal[V](ctx, bt, true) +} + +func (bt *baseTopicsClient[V]) GetName() string { + return bt.name +} + +func (bt *baseTopicsClient[V]) Close(ctx context.Context) error { + return releaseTopicInternal[V](ctx, bt, false) +} + +func (bt *baseTopicsClient[V]) String() string { + return fmt.Sprintf("topic{name=%s, topicID=%d, isReleased=%v, %v}", bt.name, bt.topicID, bt.isReleased, bt.topicOpts) +} + +func (bt *baseTopicsClient[V]) CreatePublisher(ctx context.Context, options ...func(cache *publisher.Options)) (Publisher[V], error) { + return CreatePublisher[V](ctx, bt.session, bt.name, options...) +} + +func (bt *baseTopicsClient[V]) CreateSubscriber(ctx context.Context, options ...func(cache *subscriber.Options)) (Subscriber[V], error) { + return CreateSubscriber[V](ctx, bt.session, bt.name, options...) +} + +func (bt *baseTopicsClient[V]) CreateSubscriberGroup(ctx context.Context, subscriberGroup string, options ...func(o *subscribergroup.Options)) error { + return CreateSubscriberGroup(ctx, bt.session, bt.name, subscriberGroup, options...) +} + +func (bt *baseTopicsClient[V]) DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error { + return DestroySubscriberGroup(ctx, bt.session, subscriberGroup) +} + +func newPublisher[V any](session *Session, bt *baseTopicsClient[V], result *publisher.EnsurePublisherResult, topicName string, options *publisher.Options) (Publisher[V], error) { + tp := &topicPublisher[V]{ + namedTopic: bt, + publisherID: result.PublisherID, + session: session, + options: options, + valueSerializer: NewSerializer[V](session.sessOpts.Format), + topicName: topicName, + proxyID: result.ProxyID, + channelCount: result.ChannelCount, + isClosed: false, + } + session.mapMutex.Lock() + defer session.mapMutex.Unlock() + session.publishers[result.PublisherID] = tp + + session.publisherIDMap.Add(tp.publisherID, tp.proxyID) + return tp, nil +} + +func newSubscriber[V any](session *Session, bt *baseTopicsClient[V], result *subscriber.EnsureSubscriberResult, topicName string, options *subscriber.Options) (Subscriber[V], error) { + ts := &topicSubscriber[V]{ + namedTopic: bt, + SubscriberID: result.SubscriberID, + UUID: result.UUID, + session: session, + options: options, + valueSerializer: NewSerializer[V](session.sessOpts.Format), + topicName: topicName, + proxyID: result.ProxyID, + disconnected: true, + isClosed: false, + } + + session.mapMutex.Lock() + defer session.mapMutex.Unlock() + session.subscribers[result.SubscriberID] = ts + + session.subscriberIDMap.Add(ts.SubscriberID, ts.proxyID) + + return ts, nil +} + +type topicSubscriber[V any] struct { + session *Session + namedTopic *baseTopicsClient[V] // may be nil if created outside topic + topicName string + proxyID int32 + SubscriberID int64 + SubscriberGroupID int64 + channelCount int32 + options *subscriber.Options + valueSerializer Serializer[V] + UUID string + mutex sync.RWMutex + disconnected bool + isClosed bool + lifecycleListenersV1 []*SubscriberLifecycleListener[V] +} + +func (ts *topicSubscriber[V]) String() string { + return fmt.Sprintf("subscriber{subscriberID=%d, subscriberGroup=%d, topicName=%s, channelCount=%d, options=%v}", ts.SubscriberID, ts.SubscriberGroupID, + ts.topicName, ts.channelCount, ts.options) +} + +func (ts *topicSubscriber[V]) Close(ctx context.Context) error { + if ts.isClosed { + return ErrSubscriberClosed + } + err := ts.ensureConnected() + if err != nil { + return err + } + + ts.isClosed = true + ts.session.subscriberIDMap.Remove(ts.SubscriberID) + + ts.session.mapMutex.Lock() + delete(ts.session.publishers, ts.SubscriberID) + ts.session.mapMutex.Unlock() + + ts.generateSubscriberLifecycleEvent(ts, SubscriberReleased) + + return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) +} + +func (ts *topicSubscriber[V]) DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error { + return ts.session.v1StreamManagerTopics.destroySubscriberGroup(ctx, ts.proxyID, subscriberGroup) +} + +func (ts *topicSubscriber[V]) isDisconnected() bool { + ts.mutex.Lock() + defer ts.mutex.Unlock() + return ts.disconnected +} + +// ensureConnected ensures the topic is connected. +func (ts *topicSubscriber[V]) ensureConnected() error { + if !ts.isDisconnected() { + return nil + } + + return nil +} + +func newBaseTopicsClient[V any](ctx context.Context, session *Session, queueName string, topicID int32, topicOpts *topic.Options) (*baseTopicsClient[V], error) { + if session.closed { + return nil, ErrClosed + } + + bq := baseTopicsClient[V]{ + ctx: ctx, + name: queueName, + topicID: topicID, + session: session, + valueSerializer: NewSerializer[V](session.sessOpts.Format), + mutex: &sync.RWMutex{}, + topicOpts: topicOpts, + } + + return &bq, nil +} + +func ensurePublisherOptions(options ...func(cache *publisher.Options)) *publisher.Options { + publisherOptions := &publisher.Options{ + ChannelCount: defaultChannelCount, + Ordering: &publisher.OrderByDefault{}, + } + + // apply any cache options + for _, f := range options { + f(publisherOptions) + } + + return publisherOptions +} + +// CreatePublisher creates a topic publisher when provided a topicName. You do not have to had created +// a topic, but it is equivalent to called CreatePublisher on a [NamedTopic]. +func CreatePublisher[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *publisher.Options)) (Publisher[V], error) { + publisherOptions := ensurePublisherOptions(options...) + + if session.v1StreamManagerTopics == nil { + if err := ensureV1StreamManagerTopics(session); err != nil { + return nil, err + } + } + + result, err := session.v1StreamManagerTopics.ensurePublisher(ctx, topicName, publisherOptions.ChannelCount) + if err != nil { + return nil, err + } + + return newPublisher[V](session, nil, result, topicName, publisherOptions) +} + +// CreatSubscriberWithTransformer creates a subscriber which will transform the value from the topic using +// the supplied extractor. +func CreatSubscriberWithTransformer[E any](ctx context.Context, session *Session, topicName string, + extractor extractors.ValueExtractor[any, E], options ...func(cache *subscriber.Options)) (Subscriber[E], error) { + + binExtractor, err := session.genericSerializer.Serialize(extractor) + if err != nil { + return nil, err + } + + options = append(options, subscriber.WithTransformer(binExtractor)) + + return CreateSubscriber[E](ctx, session, topicName, options...) +} + +// CreateSubscriber creates a topic subscriber. +func CreateSubscriber[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *subscriber.Options)) (Subscriber[V], error) { + var ( + subscriberOptions = &subscriber.Options{} + binFilter []byte + err error + ) + + // apply any subscriber options + for _, f := range options { + f(subscriberOptions) + } + + if session.v1StreamManagerTopics == nil { + if err = ensureV1StreamManagerTopics(session); err != nil { + return nil, err + } + } + + if subscriberOptions.Filter != nil { + binFilter, err = session.genericSerializer.Serialize(subscriberOptions.Filter) + if err != nil { + return nil, err + } + } + + result, err := session.v1StreamManagerTopics.ensureSubscriber(ctx, topicName, subscriberOptions.SubscriberGroup, binFilter) + if err != nil { + return nil, err + } + + return newSubscriber[V](session, nil, result, topicName, subscriberOptions) +} + +// CreateSubscriberGroup creates a topic subscriber group. +func CreateSubscriberGroup(ctx context.Context, session *Session, topicName string, subscriberGroup string, options ...func(cache *subscribergroup.Options)) error { + var ( + subscriberGroupOptions = &subscribergroup.Options{} + binFilter []byte + err error + ) + + // apply any subscriber options + for _, f := range options { + f(subscriberGroupOptions) + } + + if session.v1StreamManagerTopics == nil { + if err = ensureV1StreamManagerTopics(session); err != nil { + return err + } + } + + if subscriberGroupOptions.Filter != nil { + binFilter, err = session.genericSerializer.Serialize(subscriberGroupOptions.Filter) + if err != nil { + return err + } + } + + return session.v1StreamManagerTopics.ensureSubscriberGroup(ctx, topicName, subscriberGroup, binFilter) +} + +// DestroySubscriberGroup destroys a subscriber group. +func DestroySubscriberGroup(ctx context.Context, session *Session, subscriberGroup string) error { + if session.v1StreamManagerTopics == nil { + if err := ensureV1StreamManagerTopics(session); err != nil { + return err + } + } + + // TODO: Is this valid???, how can we determine the proxyID if we don't have a subscriber + return session.v1StreamManagerTopics.destroySubscriberGroup(ctx, 0, subscriberGroup) +} + +// ensurePublisher ensures a publisher. +func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, channelCount int32) (*publisher.EnsurePublisherResult, error) { + req, err := m.newEnsurePublisherRequest(topicName, channelCount) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsurePublisher) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, false) + if err1 != nil { + return nil, err + } + + var message = &pb1topics.EnsurePublisherResponse{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + return &publisher.EnsurePublisherResult{ + PublisherID: message.PublisherId, + ProxyID: message.ProxyId, + ChannelCount: message.ChannelCount, + }, nil +} + +// ensureSubscriber ensures a subscriber. +func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string, subscriberGroup *string, binFilter []byte) (*subscriber.EnsureSubscriberResult, error) { + req, err := m.newEnsureSubscriberRequest(topicName, subscriberGroup, binFilter) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscriber) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, false) + if err1 != nil { + return nil, err + } + + var message = &pb1topics.EnsureSubscriberResponse{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure subscriber response", err) + return nil, err + } + + s := subscriber.EnsureSubscriberResult{ProxyID: message.ProxyId} + + if message.SubscriberId != nil { + s.SubscriberID = message.SubscriberId.Id + s.UUID = string(message.SubscriberId.Uuid) + } + + return &s, nil +} + +// ensureSubscriber ensures a subscriber. +func (m *streamManagerV1) ensureSubscriberGroup(ctx context.Context, topicName string, subscriberGroup string, binFilter []byte) error { + req, err := m.newEnsureSubscriberGroupRequest(topicName, subscriberGroup, binFilter) + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscriberGroup) + if err != nil { + return err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + // only a complete for create subscriber group + _, err = waitForResponse(newCtx, requestType.ch, false) + + return err +} + +// destroySubscriberGroup destroys a subscriber. +func (m *streamManagerV1) destroySubscriberGroup(ctx context.Context, proxyID int32, subscriberGroup string) error { + req, err := m.newDestroySubscriberGroupRequest(proxyID, subscriberGroup) + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_DestroySubscriberGroup) + if err != nil { + return err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + // only a complete for destroy subscriber group + _, err = waitForResponse(newCtx, requestType.ch, false) + + return err +} + +// destroyPublisher destroyed a publisher. +func (m *streamManagerV1) destroyPublisherOrSubscriber(ctx context.Context, proxyID int32, reqType pb1topics.TopicServiceRequestType) error { + req, err := m.newDestroyPublisherOrSubscriberRequest(proxyID, reqType) + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, reqType) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + _, err1 := waitForResponse(newCtx, requestType.ch) + if err1 != nil { + return err + } + + return nil +} + +// +//// ensureSubscriber ensures a subscriber. +//func (m *streamManagerV1) ensureSubscriptionRequest(ctx context.Context, subscriptionID int64, force bool) (*subscriber.EnsureSubscriberResult, error) { +// req, err := m.newEnsureSubscriptionRequest(subscriptionID, force) +// if err != nil { +// return nil, err +// } +// +// requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscription) +// +// newCtx, cancel := m.session.ensureContext(ctx) +// if cancel != nil { +// defer cancel() +// } +// +// defer m.cleanupRequest(req.Id) +// +// result, err1 := waitForResponse(newCtx, requestType.ch, false) +// if err1 != nil { +// return nil, err +// } +// +// var message = &pb1topics.EnsureSubscriptionRequest{} +// if err = result.UnmarshalTo(message); err != nil { +// err = getUnmarshallError("ensure subscriber response", err) +// return nil, err +// } +// +// s := subscriber.EnsureSubscriberResult{ProxyID: message.ProxyId} +// +// if message.SubscriberId != nil { +// s.SubscriberID = message.SubscriberId.Id +// s.UUID = string(message.SubscriberId.Uuid) +// } +// +// return &s, nil +//} + +// ensureTopic issues the ensure queue request. This must be done before any requests to access queues can be issued. +func (m *streamManagerV1) ensureTopic(ctx context.Context, topicName string) (*int32, error) { + return m.ensure(ctx, topicName, m.session.topicIDMap, -1, true) +} + +func ensureTopicInternal[V any](ctx context.Context, session *Session, topicName string, topicsOpts *topic.Options) (*baseTopicsClient[V], error) { + if err := ensureV1StreamManagerTopics(session); err != nil { + return nil, err + } + + topicID, err := session.v1StreamManagerTopics.ensureTopic(ctx, topicName) + if err != nil { + return nil, err + } + + // ensure channels + _, err = session.v1StreamManagerTopics.ensureTopicChannels(ctx, topicName, topicsOpts.ChannelCount) + if err != nil { + return nil, err + } + + return newBaseTopicsClient[V](ctx, session, topicName, *topicID, topicsOpts) +} + +// ensure ensures a queue or cache. +func (m *streamManagerV1) ensureTopicChannels(ctx context.Context, topicName string, channelCount int32) (*int32, error) { + var ID *int32 + + req, err := m.newEnsureTopicChannelRequest(topicName, channelCount) + + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureChannelCount) + + if err != nil { + return nil, err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, true) + if err1 != nil { + return nil, err1 + } + + var message = &wrapperspb.Int32Value{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + return ID, nil +} + +// newPublishRequest publishes a message to a topic. +func (m *streamManagerV1) publishToTopic(ctx context.Context, proxyID int32, publishChannel int32, value []byte) (*publisher.PublishStatus, error) { + req, err := m.newPublishRequest(proxyID, publishChannel, value) + + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_Publish) + + if err != nil { + return nil, err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch) + if err1 != nil { + return nil, err1 + } + + var message = &pb1topics.PublishResult{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + return &publisher.PublishStatus{PublishedChannel: publishChannel, RemainingCapacity: message.RemainingCapacity}, nil +} + +func ensureV1StreamManagerTopics(session *Session) error { + session.connectMutex.Lock() + defer session.connectMutex.Unlock() + if session.v1StreamManagerTopics == nil { + topicsManger, err := newStreamManagerV1(session, topicServiceProtocol) + if err != nil { + if strings.Contains(err.Error(), "Method not found") { + return ErrTopicsNoSupported + } + return err + } + session.v1StreamManagerTopics = topicsManger + } + + return nil +} + +func releaseTopicInternal[V any](ctx context.Context, bt *baseTopicsClient[V], destroy bool) error { + if bt.isDestroyed || bt.isReleased { + return ErrTopicDestroyedOrReleased + } + + bt.session.mapMutex.Lock() + defer bt.session.mapMutex.Unlock() + + if destroy { + newCtx, cancel := bt.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + err := bt.session.v1StreamManagerTopics.genericTopicRequest(newCtx, pb1topics.TopicServiceRequestType_DestroyTopic, bt.name) + if err != nil { + return err + } + bt.isDestroyed = true + bt.generateTopicLifecycleEvent(nil, TopicDestroyed) + } else { + if existingTopic, ok := bt.session.topics[bt.name]; ok { + bt.generateTopicLifecycleEvent(existingTopic, TopicReleased) + bt.isReleased = true + } + } + + delete(bt.session.topics, bt.name) + bt.session.topicIDMap.Remove(bt.name) + + return nil +} + +func (m *streamManagerV1) newEnsurePublisherRequest(topicName string, channelCount int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsurePublisherRequest{ + Topic: topicName, + ChannelCount: channelCount, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsurePublisher, anyReq) +} + +func (m *streamManagerV1) newDestroyPublisherOrSubscriberRequest(proxyID int32, requestType pb1topics.TopicServiceRequestType) (*pb1.ProxyRequest, error) { + req := &wrapperspb.Int32Value{ + Value: proxyID, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest("", requestType, anyReq) +} + +func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, subscriberGroup *string, binFilter []byte) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureSubscriberRequest{ + Topic: topicName, + Filter: binFilter, + SubscriberGroup: subscriberGroup, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSubscriber, anyReq) +} + +func (m *streamManagerV1) newEnsureSubscriberGroupRequest(topicName string, subscriberGroup string, binFilter []byte) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureSubscriberGroupRequest{ + SubscriberGroup: subscriberGroup, + Filter: binFilter, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSubscriberGroup, anyReq) +} + +func (m *streamManagerV1) newDestroySubscriberGroupRequest(proxyID int32, subscriberGroup string) (*pb1.ProxyRequest, error) { + req := &wrapperspb.StringValue{ + Value: subscriberGroup, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyPublisherRequest(proxyID, pb1topics.TopicServiceRequestType_DestroySubscriberGroup, anyReq) + + //return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_DestroySubscriberGroup, anyReq) +} + +//func (m *streamManagerV1) newInitializeSubscriptionRequest(subscriptionID int64, force bool) (*pb1.ProxyRequest, error) { +// req := &pb1topics.InitializeSubscriptionRequest{ +// SubscriptionId: subscriptionID, +// ForceReconnect: force, +// } +// +// anyReq, err := anypb.New(req) +// if err != nil { +// return nil, err +// } +// return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureSubscription, anyReq) +//} + +// genericTopicRequest issues a generic topic request that is further defined by the reqType. +func (m *streamManagerV1) genericTopicRequest(ctx context.Context, reqType pb1topics.TopicServiceRequestType, topic string) error { + var ( + err error + req *pb1.ProxyRequest + isDestroy = false + value *anypb.Any + ) + + if reqType == pb1topics.TopicServiceRequestType_DestroyTopic { + isDestroy = true + } + + if isDestroy { + // destroy requires sending of the topic name in the message + value, err = stringToAny(topic) + if err != nil { + return err + } + req, err = m.newWrapperProxyTopicsRequest(topic, reqType, value) + } else { + req, err = m.newGenericNamedTopicRequest(topic, reqType) + } + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, reqType) + if err != nil { + return err + } + + // we must now wait for a response + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + // remove the entry from the channel + defer m.cleanupRequest(req.Id) + + if isDestroy { + // special case for destroy + return nil + } + + _, err1 := waitForResponse(newCtx, requestType.ch) + if err1 != nil { + return err1 + } + + return nil +} + +func (m *streamManagerV1) newWrapperProxyTopicsRequest(topicName string, requestType pb1topics.TopicServiceRequestType, message *anypb.Any) (*pb1.ProxyRequest, error) { + var ( + topicID *int32 + zeroTopic int32 + noTopicRequired = requestType == pb1topics.TopicServiceRequestType_DestroyTopic || + requestType == pb1topics.TopicServiceRequestType_EnsurePublisher || + requestType == pb1topics.TopicServiceRequestType_EnsureSubscription || + requestType == pb1topics.TopicServiceRequestType_EnsureSubscriber || + requestType == pb1topics.TopicServiceRequestType_DestroySubscriberGroup + ) + + // validate the topic ID if it is not an ensure queue request + if topicName != "" && !noTopicRequired { + topicID = m.session.getTopicID(topicName) + if topicID == nil { + return nil, getTopicIDMessage(topicName) + } + } + + // special cases for topic requests that require no topic + if noTopicRequired { + topicID = &zeroTopic + } + + ncRequest, err := newNamedTopicRequest(topicID, requestType, message) + + if err != nil { + return nil, err + } + + return m.newProxyRequest(ncRequest), nil +} + +func (m *streamManagerV1) newWrapperProxyPublisherRequest(proxyID int32, requestType pb1topics.TopicServiceRequestType, message *anypb.Any) (*pb1.ProxyRequest, error) { + ncRequest, err := newNamedTopicRequest(&proxyID, requestType, message) + + if err != nil { + return nil, err + } + + return m.newProxyRequest(ncRequest), nil +} + +func stringToAny(topicName string) (*anypb.Any, error) { + strValue := wrapperspb.String(topicName) + return anypb.New(strValue) +} + +func newNamedTopicRequest(topicID *int32, reqType pb1topics.TopicServiceRequestType, message *anypb.Any) (*anypb.Any, error) { + req := &pb1topics.TopicServiceRequest{ + Type: reqType, + ProxyId: topicID, + Message: message, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return anyReq, nil +} + +// submitTopicRequest submits a request to the stream manager and returns named topic request. +func (m *streamManagerV1) submitTopicRequest(req *pb1.ProxyRequest, requestType pb1topics.TopicServiceRequestType) (proxyRequestChannel, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + // create a channel for the response + ch := make(chan responseMessage) + + r := proxyRequestChannel{ch: ch} + + // save the request in the map keyed by request id + m.requests[req.Id] = r + m.session.debugConnection("id: %v submit topic request: %v %v", req.Id, requestType, req) + + return r, m.eventStream.grpcStream.Send(req) +} diff --git a/coherence/topics_events.go b/coherence/topics_events.go new file mode 100644 index 0000000..17ae5ff --- /dev/null +++ b/coherence/topics_events.go @@ -0,0 +1,174 @@ +/* + * Copyright (c) 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 coherence + +import ( + "context" + "fmt" +) + +const ( + // TopicDestroyed raised when a queue is destroyed usually as a result of a call to NamedTopic.Destroy(). + TopicDestroyed TopicLifecycleEventType = "topic_destroyed" + + // TopicReleased raised when a topic is released but the session. + TopicReleased TopicLifecycleEventType = "topic_released" +) + +type TopicLifecycleEventType string + +type TopicLifecycleEvent[V any] interface { + Source() NamedTopic[V] + Type() TopicLifecycleEventType +} + +type topicLifecycleEvent[V any] struct { + // source the source of the event + source NamedTopic[V] + + // Type of this event's TopicLifecycleEventType + eventType TopicLifecycleEventType +} + +// Type returns the TopicLifecycleEvent for this [TopicLifecycleEventType]. +func (l *topicLifecycleEvent[V]) Type() TopicLifecycleEventType { + return l.eventType +} + +// Source returns the source of this [QueueLifecycleEvent]. +func (l *topicLifecycleEvent[V]) Source() NamedTopic[V] { + return l.source +} + +// String returns a string representation of a MapLifecycleEvent. +func (l *topicLifecycleEvent[V]) String() string { + return fmt.Sprintf("TopicLifecycleEvent{source=%v, type=%s}", l.Source().GetName(), l.Type()) +} + +// TopicLifecycleListener allows registering callbacks to be notified when lifecycle events +// (destroyed, released) occur against a [NamedTopic]. +type TopicLifecycleListener[V any] interface { + // OnAny registers a callback that will be notified when any [NamedTopic] event occurs. + OnAny(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] + + // OnDestroyed registers a callback that will be notified when a [NamedTopic] is destroyed. + OnDestroyed(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] + + // OnReleased registers a callback that will be notified when a [NamedTopic] is released. + OnReleased(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] + + getEmitter() *eventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]] +} + +// NewTopicLifecycleListener creates and returns a pointer to a new [TopicLifecycleListener] instance. +func NewTopicLifecycleListener[V any]() TopicLifecycleListener[V] { + return &topicLifecycleListener[V]{newEventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]]()} +} + +type topicLifecycleListener[V any] struct { //lint:ignore U1000 - required due to linter issues with generics + emitter *eventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]] +} + +// OnAny registers a callback that will be notified when any [NamedTopic] event occurs. +func (t *topicLifecycleListener[V]) OnAny(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + return t.OnDestroyed(callback).OnReleased(callback) +} + +// OnDestroyed registers a callback that will be notified when a[NamedTopic] is destroyed. +func (t *topicLifecycleListener[V]) OnDestroyed(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + return t.on(TopicDestroyed, callback) +} + +// OnReleased registers a callback that will be notified when a[NamedTopic] is released. +func (t *topicLifecycleListener[V]) OnReleased(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + return t.on(TopicReleased, callback) +} + +func (t *topicLifecycleListener[V]) getEmitter() *eventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]] { + return t.emitter +} + +// on registers a callback for the specified [TopicLifecycleEventType]. +func (t *topicLifecycleListener[V]) on(event TopicLifecycleEventType, callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + t.emitter.on(event, callback) + return t +} + +func (bt *baseTopicsClient[V]) AddLifecycleListener(listener TopicLifecycleListener[V]) error { + if bt.isDestroyed || bt.isReleased { + return ErrTopicDestroyedOrReleased + } + + bt.addLifecycleListener(listener) + return nil +} + +func (bt *baseTopicsClient[V]) RemoveLifecycleListener(listener TopicLifecycleListener[V]) error { + if bt.isDestroyed || bt.isReleased { + return ErrTopicDestroyedOrReleased + } + + bt.removeLifecycleListener(listener) + return nil +} + +// addLifecycleListener adds the specified [TopicLifecycleListener]. +func (bt *baseTopicsClient[V]) addLifecycleListener(listener TopicLifecycleListener[V]) { + bt.mutex.Lock() + defer bt.mutex.Unlock() + + for _, e := range bt.lifecycleListenersV1 { + if *e == listener { + return + } + } + bt.lifecycleListenersV1 = append(bt.lifecycleListenersV1, &listener) +} + +// removeLifecycleListener removes the specified [TopicLifecycleListener]. +func (bt *baseTopicsClient[V]) removeLifecycleListener(listener TopicLifecycleListener[V]) { + bt.mutex.Lock() + defer bt.mutex.Unlock() + + idx := -1 + listeners := bt.lifecycleListenersV1 + for i, c := range listeners { + if *c == listener { + idx = i + break + } + } + if idx != -1 { + result := append(listeners[:idx], listeners[idx+1:]...) + bt.lifecycleListenersV1 = result + } +} + +func newTopicLifecycleEvent[V any](nt NamedTopic[V], eventType TopicLifecycleEventType) TopicLifecycleEvent[V] { + return &topicLifecycleEvent[V]{source: nt, eventType: eventType} +} + +// generateTopicLifecycleEvent emits the topic lifecycle events. +func (bt *baseTopicsClient[V]) generateTopicLifecycleEvent(client interface{}, eventType TopicLifecycleEventType) { + listeners := bt.lifecycleListenersV1 + + if namedT, ok := client.(NamedTopic[V]); ok || client == nil { + event := newTopicLifecycleEvent[V](namedT, eventType) + for _, l := range listeners { + e := *l + e.getEmitter().emit(eventType, event) + } + + if eventType == TopicDestroyed { + _ = releaseTopicInternal[V](context.Background(), bt, false) + } + } +} + +type TopicEventSubmitter interface { + generateTopicLifecycleEvent(client interface{}, eventType TopicLifecycleEventType) +} diff --git a/coherence/v1client.go b/coherence/v1client.go index 69b4bee..a38375b 100644 --- a/coherence/v1client.go +++ b/coherence/v1client.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" "github.com/oracle/coherence-go-client/v2/proto/v1" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" "google.golang.org/grpc/codes" @@ -43,10 +44,12 @@ const ( protocolVersion = 1 cacheServiceProtocol V1ProxyProtocol = "CacheService" queueServiceProtocol V1ProxyProtocol = "QueueService" + topicServiceProtocol V1ProxyProtocol = "TopicService" errorFormat = "error: %v" responseDebug = "received response: %v" namedCacheResponseTypeURL = "type.googleapis.com/coherence.cache.v1.NamedCacheResponse" namedQueueResponseTypeURL = "type.googleapis.com/coherence.concurrent.queue.v1.NamedQueueResponse" + namedTopicResponseTypeURL = "type.googleapis.com/coherence.topic.v1.TopicServiceResponse" ) // responseMessages is a response received by a waiting client. @@ -54,6 +57,7 @@ type responseMessage struct { message *anypb.Any namedCacheResponse *pb1.NamedCacheResponse namedQueueResponse *pb1.NamedQueueResponse + namedTopicResponse *pb1topics.TopicServiceResponse complete bool err *string } @@ -64,6 +68,10 @@ func (rm responseMessage) String() string { if rm.namedCacheResponse != nil { sb.WriteString(fmt.Sprintf(", cacheId=%v", rm.namedCacheResponse.CacheId)) + } else if rm.namedTopicResponse != nil { + sb.WriteString(fmt.Sprintf(", proxyID=%v", rm.namedTopicResponse.ProxyId)) + } else if rm.namedQueueResponse != nil { + sb.WriteString(fmt.Sprintf(", queueId=%v", rm.namedQueueResponse.QueueId)) } sb.WriteString("}") @@ -152,10 +160,8 @@ func (m *streamManagerV1) ensureStream() (*eventStreamV1, error) { // check that we received an InitResponse response := proxyResponse.GetInit() if response == nil { - if err != nil { - cancel() - return nil, errors.New("did not receive an InitResponse") - } + cancel() + return nil, errors.New("did not receive an InitResponse") } // save the server information received @@ -226,6 +232,13 @@ func (m *streamManagerV1) processResponseMessage(id int64, resp *responseMessage return } resp.namedQueueResponse = &namedQueueResponse + case namedTopicResponseTypeURL: + var namedTopicResponse pb1topics.TopicServiceResponse + if err := resp.message.UnmarshalTo(&namedTopicResponse); err != nil { + logMessage(WARNING, "%v", getUnmarshallError("namedTopicResponse", err)) + return + } + resp.namedTopicResponse = &namedTopicResponse default: logMessage(WARNING, "Unknown message type: %v", resp.message.TypeUrl) @@ -325,7 +338,6 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { // process queue event if reqID == 0 && resp.namedQueueResponse != nil { - // find the queueName from the queueID queueName := m.session.queueIDMap.KeyFromValue(resp.namedQueueResponse.QueueId) if queueName == nil { @@ -345,6 +357,12 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { return } + // process topic event + if reqID == 0 && resp.namedTopicResponse != nil { + processTopicEvent(m, resp) + return + } + m.session.debugConnection("id: %v Response: %v", reqID, resp) // received a named cache or queue response, so write the response to the channel for the originating request @@ -352,13 +370,117 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { defer m.mutex.Unlock() if e, ok := m.requests[reqID]; ok { - // request exists e.ch <- *resp } else { m.session.debugConnection("found request %v (%v) in response but no request exists", *resp, reqID) } } +// processTopicEvent processes a topic event. +func processTopicEvent(m *streamManagerV1, resp *responseMessage) { + // find the topicName from the ProxyID, this could bte the topicID, publisherID or SubscriberID + topicName := m.session.topicIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) + + if topicName != nil { + // must be a topic + if existingTopic, ok := m.session.topics[*topicName]; ok { + if topicEventSubmitter, ok2 := existingTopic.(TopicEventSubmitter); ok2 { + switch resp.namedTopicResponse.Type { + case pb1topics.ResponseType_Event: + var eventType = &pb1topics.NamedTopicEvent{} + if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { + err = getUnmarshallError("ensure response", err) + m.session.debugConnection("cannot unmarshal topic response topic for topic %v: %v", topicName, err) + } else { + if eventType.Type == pb1topics.TopicEventType_TopicDestroyed { + topicEventSubmitter.generateTopicLifecycleEvent(existingTopic, TopicDestroyed) + } + } + } + } + } + return + } + + // search for publisher ID via ProxyID + publisherID := m.session.publisherIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) + if publisherID != nil { + processPublisherEvent(m, resp, publisherID) + return + } + + // search for subscriber ID for ProxyID + subscriberID := m.session.subscriberIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) + if subscriberID != nil { + processSubscriberEvent(m, resp, subscriberID) + return + } + + m.session.debugConnection("cannot find topic, subscriber or publisher for message ID %v and type %v", + resp.namedTopicResponse.ProxyId, resp.namedTopicResponse.Type) +} + +func processPublisherEvent(m *streamManagerV1, resp *responseMessage, publisherID *int64) { + m.session.debugConnection("Received message type %v for publisher id %v", resp.namedTopicResponse.Type, *publisherID) + if existingPublisher, ok := m.session.publishers[*publisherID]; ok { + if publisherEventSubmitter, ok2 := existingPublisher.(PublisherEventSubmitter); ok2 { + switch resp.namedTopicResponse.Type { + case pb1topics.ResponseType_Event: + var eventType = &pb1topics.PublisherEvent{} + if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { + err = getUnmarshallError("ensure response", err) + m.session.debugConnection("cannot unmarshal topic response topic for publisher %v: %v", publisherID, err) + return + } + switch eventType.Type { + case pb1topics.PublisherEventType_PublisherDestroyed: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherDestroyed) + case pb1topics.PublisherEventType_PublisherConnected: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherConnected) + case pb1topics.PublisherEventType_PublisherDisconnected: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherDisconnected) + case pb1topics.PublisherEventType_PublisherChannelsFreed: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherChannelsFreed) + } + } + } + } +} +func processSubscriberEvent(m *streamManagerV1, resp *responseMessage, subscriberID *int64) { + m.session.debugConnection("Received message type %v for subscriber id %v", resp.namedTopicResponse.Type, *subscriberID) + if existingSubscriber, ok := m.session.subscribers[*subscriberID]; ok { + if subscriberEventSubmitter, ok2 := existingSubscriber.(SubscriberEventSubmitter); ok2 { + switch resp.namedTopicResponse.Type { + case pb1topics.ResponseType_Event: + var eventType = &pb1topics.SubscriberEvent{} + if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { + err = getUnmarshallError("ensure response", err) + m.session.debugConnection("cannot unmarshal topic response topic for subscriber %v: %v", subscriberID, err) + return + } + switch eventType.Type { + case pb1topics.SubscriberEventType_SubscriberChannelAllocation: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelAllocation) + case pb1topics.SubscriberEventType_SubscriberChannelHead: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelHead) + case pb1topics.SubscriberEventType_SubscriberChannelPopulated: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelPopulated) + case pb1topics.SubscriberEventType_SubscriberChannelsLost: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelsLost) + case pb1topics.SubscriberEventType_SubscriberDestroyed: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberDestroyed) + case pb1topics.SubscriberEventType_SubscriberDisconnected: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberDisconnected) + case pb1topics.SubscriberEventType_SubscriberGroupDestroyed: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberGroupDestroyed) + case pb1topics.SubscriberEventType_SubscriberUnsubscribed: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberUnsubscribed) + } + } + } + } +} + // submitRequest submits a request to the stream manager and returns named cache request. func (m *streamManagerV1) submitRequest(req *pb1.ProxyRequest, requestType pb1.NamedCacheRequestType) (proxyRequestChannel, error) { m.mutex.Lock() @@ -383,19 +505,25 @@ func (m *streamManagerV1) ensureCache(ctx context.Context, cache string) (*int32 } // ensure ensures a queue or cache. -func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap[string, int32], queueType NamedQueueType) (*int32, error) { +func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap[string, int32], queueType NamedQueueType, isTopicFlag ...bool) (*int32, error) { var ( ID *int32 err error req *v1.ProxyRequest isQueue = queueType >= 0 + isTopic bool requestType proxyRequestChannel ) + if len(isTopicFlag) > 0 && isTopicFlag[0] { + isTopic = true + } + if isQueue { req, err = m.newEnsureQueueRequest(name, queueType) + } else if isTopic { + req, err = m.newEnsureTopicRequest(name) } else { - // cache req, err = m.newEnsureCacheRequest(name) } @@ -405,6 +533,8 @@ func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap if isQueue { requestType, err = m.submitQueueRequest(req, pb1.NamedQueueRequestType_EnsureQueue) + } else if isTopic { + requestType, err = m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureTopic) } else { requestType, err = m.submitRequest(req, pb1.NamedCacheRequestType_EnsureCache) } @@ -1216,7 +1346,7 @@ func (m *streamManagerV1) cleanupRequest(reqID int64) { close(requestType.ch) } -// defaultFunction returns the default named cache message +// defaultFunction returns the default named cache, queue or topic message. var defaultFunction = func(resp responseMessage) *anypb.Any { if resp.namedCacheResponse != nil && resp.namedCacheResponse.Message != nil { return resp.namedCacheResponse.Message @@ -1224,6 +1354,9 @@ var defaultFunction = func(resp responseMessage) *anypb.Any { if resp.namedQueueResponse != nil && resp.namedQueueResponse.Message != nil { return resp.namedQueueResponse.Message } + if resp.namedTopicResponse != nil && resp.namedTopicResponse.Message != nil { + return resp.namedTopicResponse.Message + } return nil } @@ -1232,7 +1365,7 @@ func waitForResponse(newCtx context.Context, ch chan responseMessage, ensure ... var ( err error result *anypb.Any - isEnsure = len(ensure) != 0 + isEnsure = len(ensure) != 0 && ensure[0] ) // wait until we get a complete request, or we time out @@ -1247,7 +1380,7 @@ func waitForResponse(newCtx context.Context, ch chan responseMessage, ensure ... resp.complete = true } - if resp.namedCacheResponse != nil || resp.namedQueueResponse != nil { + if resp.namedCacheResponse != nil || resp.namedQueueResponse != nil || resp.namedTopicResponse != nil { // unpack the result message if we have valid named cache response if !isEnsure { // standard case where we want to return the named cache/queue result message @@ -1256,6 +1389,8 @@ func waitForResponse(newCtx context.Context, ch chan responseMessage, ensure ... // special case for ensure, return the cache id or queue id, and it will be handled by ensure if resp.namedCacheResponse != nil { result, err = anypb.New(wrapperspb.Int32(resp.namedCacheResponse.CacheId)) + } else if resp.namedTopicResponse != nil { + result, err = anypb.New(wrapperspb.Int32(resp.namedTopicResponse.ProxyId)) } else { result, err = anypb.New(wrapperspb.Int32(resp.namedQueueResponse.QueueId)) } @@ -1405,6 +1540,10 @@ func getQueueIDMessage(cache string) error { return fmt.Errorf("unable to find queue id for queue named [%s]", cache) } +func getTopicIDMessage(topic string) error { + return fmt.Errorf("unable to find topic id for topic named [%s]", topic) +} + func getUnmarshallError(message string, err error) error { return fmt.Errorf("unable to unpack %v message %v", message, err) } diff --git a/coherence/v1queues.go b/coherence/v1queues.go index 44816b4..de77c11 100644 --- a/coherence/v1queues.go +++ b/coherence/v1queues.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ @@ -20,7 +20,7 @@ func (m *streamManagerV1) ensureQueue(ctx context.Context, queue string, queueTy return m.ensure(ctx, queue, m.session.queueIDMap, queueType) } -// submitRequest submits a request to the stream manager and returns named queue request. +// submitQueueRequest submits a request to the stream manager and returns named queue request. func (m *streamManagerV1) submitQueueRequest(req *pb1.ProxyRequest, requestType pb1.NamedQueueRequestType) (proxyRequestChannel, error) { m.mutex.Lock() defer m.mutex.Unlock() @@ -37,7 +37,7 @@ func (m *streamManagerV1) submitQueueRequest(req *pb1.ProxyRequest, requestType return r, m.eventStream.grpcStream.Send(req) } -// genericCacheRequest issues a generic request that is further defined by the reqType. +// genericQueueRequest issues a generic request that is further defined by the reqType. func (m *streamManagerV1) genericQueueRequest(ctx context.Context, reqType pb1.NamedQueueRequestType, queue string) error { req, err := m.newGenericNamedQueueRequest(queue, reqType) if err != nil { diff --git a/coherence/v1requests.go b/coherence/v1requests.go index b486bd3..35d4491 100644 --- a/coherence/v1requests.go +++ b/coherence/v1requests.go @@ -7,9 +7,13 @@ package coherence import ( + "fmt" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" + "os" + "runtime" "time" ) @@ -25,6 +29,7 @@ func (m *streamManagerV1) newInitRequest() *pb1.ProxyRequest { ProtocolVersion: protocolVersion, SupportedProtocolVersion: protocolVersion, Protocol: string(m.proxyProtocol), + Identity: generateClientMemberIdentity(), } pr := pb1.ProxyRequest{ @@ -37,6 +42,30 @@ func (m *streamManagerV1) newInitRequest() *pb1.ProxyRequest { return &pr } +func generateClientMemberIdentity() *pb1.ClientMemberIdentity { + processID := fmt.Sprintf("PID=%d, OS=%v, Arch=%v", os.Getpid(), runtime.GOOS, runtime.GOARCH) + hostname, _ := os.Hostname() + clusterName := getEnvOrNil("COHERENCE_CLUSTER") + rackName := getEnvOrNil("COHERENCE_RACK") + siteName := getEnvOrNil("COHERENCE_SITE") + + return &pb1.ClientMemberIdentity{ + ProcessName: &processID, + MachineName: &hostname, + ClusterName: clusterName, + RackName: rackName, + SiteName: siteName, + } +} + +func getEnvOrNil(key string) *string { + value := getStringValueFromEnvVarOrDefault(key, "") + if value == "" { + return nil + } + return &value +} + func (m *streamManagerV1) newEnsureCacheRequest(cache string) (*pb1.ProxyRequest, error) { req := &pb1.EnsureCacheRequest{ Cache: cache, @@ -64,6 +93,48 @@ func (m *streamManagerV1) newEnsureQueueRequest(queue string, queueType NamedQue return m.newWrapperProxyQueueRequest("", pb1.NamedQueueRequestType_EnsureQueue, anyReq) } +func (m *streamManagerV1) newEnsureTopicRequest(topicName string) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureTopicRequest{ + Topic: topicName, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + + return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureTopic, anyReq) +} + +func (m *streamManagerV1) newEnsureTopicChannelRequest(topicName string, channelCount int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureChannelCountRequest{ + Topic: &topicName, + ChannelCount: &channelCount, + RequiredCount: channelCount, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + + return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureChannelCount, anyReq) +} + +func (m *streamManagerV1) newPublishRequest(proxyID int32, publishChannel int32, value []byte) (*pb1.ProxyRequest, error) { + req := &pb1topics.PublishRequest{ + Channel: publishChannel, + Values: [][]byte{value}, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + + return m.newWrapperProxyPublisherRequest(proxyID, pb1topics.TopicServiceRequestType_Publish, anyReq) +} + func getQueueType(queueType NamedQueueType) pb1.NamedQueueType { if queueType == Queue { return pb1.NamedQueueType_Queue @@ -82,6 +153,10 @@ func (m *streamManagerV1) newGenericNamedQueueRequest(cache string, requestType return m.newWrapperProxyQueueRequest(cache, requestType, nil) } +func (m *streamManagerV1) newGenericNamedTopicRequest(topicName string, requestType pb1topics.TopicServiceRequestType) (*pb1.ProxyRequest, error) { + return m.newWrapperProxyTopicsRequest(topicName, requestType, nil) +} + func (m *streamManagerV1) newGetRequest(cache string, key []byte) (*pb1.ProxyRequest, error) { return m.newSingleValueBasedRequest(pb1.NamedCacheRequestType_Get, cache, key) } diff --git a/go.sum b/go.sum index e22d2bc..67b529a 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,6 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= diff --git a/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java b/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java index 2ff5897..56ece06 100644 --- a/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java +++ b/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ @@ -10,11 +10,13 @@ import java.io.OutputStream; import java.lang.reflect.Method; import java.net.InetSocketAddress; +import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -30,6 +32,7 @@ import com.tangosol.net.NamedMap; import com.tangosol.net.SessionConfiguration; import com.tangosol.net.management.MBeanServerProxy; +import com.tangosol.net.topic.NamedTopic; /** * A simple Http server that is deployed into a Coherence cluster @@ -70,6 +73,7 @@ public static void main(String[] args) { server.createContext("/checkCustomerCache", RestServer::checkCustomerCache); server.createContext("/isIsReadyPresent", RestServer::isIsReadyPresent); server.createContext("/populateQueue", RestServer::populateQueue); + server.createContext("/destroyTopic", RestServer::destroyTopic); server.setExecutor(null); // creates a default executor server.start(); @@ -117,6 +121,28 @@ private static void populateQueue(HttpExchange t) throws IOException { send(t, 200, "OK"); } + private static void destroyTopic(HttpExchange t) throws IOException { + try { + URI uri = t.getRequestURI(); + String path = uri.getPath(); + String[] pathComponents = path.split("/"); + + if (pathComponents.length < 3 || !pathComponents[pathComponents.length - 2].equals("destroyTopic")) { + t.sendResponseHeaders(400, -1); // Bad Request + return; + } + + String topicName = pathComponents[pathComponents.length - 1]; + + NamedTopic topic = Coherence.clusterMember().start().get().getSession().getTopic(topicName); + topic.destroy(); + } catch (Exception e) { + e.printStackTrace(); + send(t, 404, "Error: " + e.getMessage()); + } + send(t, 200, "OK"); + } + private static void ready(HttpExchange t) throws IOException { send(t, 200, "OK"); } diff --git a/java/coherence-go-test/src/main/resources/test-cache-config.xml b/java/coherence-go-test/src/main/resources/test-cache-config.xml index d6d9b77..2ea736b 100644 --- a/java/coherence-go-test/src/main/resources/test-cache-config.xml +++ b/java/coherence-go-test/src/main/resources/test-cache-config.xml @@ -1,6 +1,6 @@ @@ -27,6 +27,13 @@ + + + * + topic-scheme + + + distributed-scheme @@ -90,5 +97,18 @@ + + + topic-scheme + ${coherence.service.name Partitioned}Topic + true + ${coherence.distributed.partitioncount 257} + true + + {topic-high-units-bytes 0B} + + + + diff --git a/java/pom.xml b/java/pom.xml index 6f2e3c6..80efc47 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -222,6 +222,7 @@ -Dcoherence.management=all -Dcoherence.management.http=all -Dcoherence.management.http.port=30000 + -Dcoherence.health.http.port=6676 -Dcoherence.metrics.http.enabled=true -Dcoherence.metrics.http.port=9612 -Dcoherence.grpc.server.port=1408 @@ -270,6 +271,7 @@ -Dcoherence.log.level=9 -Dcoherence.management.http=all -Dcoherence.management.http.port=30000 + -Dcoherence.health.http.port=6677 -Dcoherence.management.refresh.expiry=1s -Dcoherence.distributed.localstorage=true -Dcoherence.metrics.http.enabled=true diff --git a/proto/topics/topic_service_messages_v1.pb.go b/proto/topics/topic_service_messages_v1.pb.go new file mode 100644 index 0000000..3962d42 --- /dev/null +++ b/proto/topics/topic_service_messages_v1.pb.go @@ -0,0 +1,3130 @@ +// +// Copyright (c) 2020, 2025, Oracle and/or its affiliates. +// +// Licensed under the Universal Permissive License v 1.0 as shown at +// https://oss.oracle.com/licenses/upl. + +// ----------------------------------------------------------------- +// Messages used by the Coherence gRPC NamedTopic Service. +// +// NOTE: If you add a new request message to this message the current +// protocol version in com.oracle.coherence.grpc.NamedTopicProtocol must +// be increased. This only needs to be done once for any given Coherence +// release. +// ----------------------------------------------------------------- + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v3.19.2 +// source: topic_service_messages_v1.proto + +package topics + +import ( + v1 "github.com/oracle/coherence-go-client/v2/proto/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + _ "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + _ "google.golang.org/protobuf/types/known/wrapperspb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// An enum representing the types of request for a Named Topic Service proxy +// +// NOTE: The index numbers for the enum elements MUST NOT BE CHANGED as +// that would break backwards compatibility. Only new index numbers can +// be added. +type TopicServiceRequestType int32 + +const ( + // An unknown message. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + TopicServiceRequestType_RequestUnknown TopicServiceRequestType = 0 + // Called to ensure a topic. + // Must be the first message called prior to any other topic requests. + // The message field must be an EnsureTopicRequest. + // The response will contain the Topic Id and an empty response field. + TopicServiceRequestType_EnsureTopic TopicServiceRequestType = 1 + // Destroy the specified topic. + // A destroy topic message must be sent with a proxy identifier of zero as it + // is targeted at the topic service proxy. + // The message field should be set to a StringValue containing the name of the topic to destroy. + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroyTopic TopicServiceRequestType = 2 + // Called to get the channel count for a topic. + // This message can be sent without ensuring the topic first and does not require + // a topic id to be set. If the topic id is set then that will be used to identify + // the topic to use, otherwise the topic name should be in the StringValue message. + // If no topic id is set, the message field is a StringValue which is the name of the + // topic to get the channel count for. + // The response will be an Int32Value containing the channel count + TopicServiceRequestType_GetChannelCount TopicServiceRequestType = 3 + // Get the durable subscriber groups for a topic + // This message can be sent without ensuring the topic first and does not require + // a topic id to be set. If the topic id is set then that will be used to identify + // the topic to use, otherwise the topic name should be in the StringValue message. + // If no topic id is set, the message field is a StringValue which is the name of the + // topic to get the subscriber groups for. + // The response will be a CollectionOfString message containing the names of the + // subscriber groups. + TopicServiceRequestType_GetSubscriberGroups TopicServiceRequestType = 4 + // Ensure that a topic has a specified number of channels + // The message field should be an EnsureChannelCount message + // The response wil be an Int32Value containing the topic channel count + TopicServiceRequestType_EnsureChannelCount TopicServiceRequestType = 5 + // Ensure a subscriber group exists for a topic. + // The message field should be an EnsureSubscriberGroupRequest message + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_EnsureSubscriberGroup TopicServiceRequestType = 6 + // Destroy a subscriber group. + // The message field should be an StringValue containing the name of the + // subscriber group to be destroyed + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroySubscriberGroup TopicServiceRequestType = 7 + // Get a count of the remaining messages in a topic for a subscriber group. + // The message field should be a GetRemainingMessagesRequest + // The response will be an Int32Value containing the count of remaining messages. + TopicServiceRequestType_GetRemainingMessages TopicServiceRequestType = 8 + // Get the tail positions for a topic + // The response will be a MapOfChannelAndPosition with a position for each channel. + TopicServiceRequestType_GetTails TopicServiceRequestType = 9 + // Called to ensure a publisher. + // Must be the first message called prior to any other publisher requests. + // The message field must be an EnsurePublisherRequest. + // The response will contain the Publisher Id and an EnsurePublisherResponse response message. + TopicServiceRequestType_EnsurePublisher TopicServiceRequestType = 10 + // Destroy the specified publisher. + // The message field must be set to an Int32Value containing the publisher identifier + // returned by the original ensure publisher request. + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroyPublisher TopicServiceRequestType = 11 + // Publish values to a topic + // The message field must be a PublishRequest + // The response will be a PublishResult message + TopicServiceRequestType_Publish TopicServiceRequestType = 12 + // Called to ensure a subscriber. + // Must be the first message called prior to any other subscriber requests. + // The message field must be an EnsureSubscriberRequest. + // The response will contain the Subscriber Id and an EnsureSubscriberResponse + // message in the response field. + TopicServiceRequestType_EnsureSubscriber TopicServiceRequestType = 13 + // Destroy the specified subscriber. + // The message field must be set to an Int32Value containing the subscriber identifier + // returned by the original ensure subscriber request. + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroySubscriber TopicServiceRequestType = 14 + // Initialize a subscriber connection + // The message field should be an InitializeSubscriptionRequest message. + // The response will be a InitializeSubscriptionResponse message. + TopicServiceRequestType_InitializeSubscription TopicServiceRequestType = 15 + // Ensure a subscriber has a subscription to the topic. + // The message field should be an EnsureSubscriptionRequest message. + // The response will be a BoolValue indicating whether the subscription exists + TopicServiceRequestType_EnsureSubscription TopicServiceRequestType = 16 + // Get the head positions for a subscriber + // The message body should be a CollectionOfInt32 specifying the channels to + // get the head positions for. + // The response will be a MapOfChannelAndPosition with a position for each channel. + TopicServiceRequestType_GetSubscriberHeads TopicServiceRequestType = 17 + // Get the last committed positions for a subscriber. + // The message field should be empty. + // The response will be a MapOfChannelAndPosition with a last committed position for each channel. + TopicServiceRequestType_GetLastCommited TopicServiceRequestType = 18 + // Obtain the channels that are owned by a subscriber. + // The message field should be empty. + // The response will be a CollectionOfInt32 containing the owned channel identifiers. + TopicServiceRequestType_GetOwnedChannels TopicServiceRequestType = 19 + // Send a heartbeat message for a subscriber. + // The message contains a BoolValue indicating whether the heartbeat should + // be sent asynchronously. + TopicServiceRequestType_SubscriberHeartbeat TopicServiceRequestType = 20 + // Determine whether a position has been committed by a subscriber. + // The message contains a ChannelAndPosition value. + // The response will be a BoolValue indicating whether the position is committed. + TopicServiceRequestType_IsPositionCommitted TopicServiceRequestType = 21 + // Peek at a value in a position. + // The message contains a ChannelAndPosition value. + // The response will be a TopicElement with the value from the position + TopicServiceRequestType_PeekAtPosition TopicServiceRequestType = 22 + // Receive values from a topic. + // The message should be a ReceiveRequest. + // The response will be a ReceiveResponse. + TopicServiceRequestType_Receive TopicServiceRequestType = 23 + // Seek a subscriber to a new position. + // The message should be a SeekRequest. + // The response will be a SeekResult. + TopicServiceRequestType_SeekSubscriber TopicServiceRequestType = 24 + // Commit a channel and position + // The message should be a ChannelAndPosition + // The response will be a CommitResponse + TopicServiceRequestType_CommitPosition TopicServiceRequestType = 25 +) + +// Enum value maps for TopicServiceRequestType. +var ( + TopicServiceRequestType_name = map[int32]string{ + 0: "RequestUnknown", + 1: "EnsureTopic", + 2: "DestroyTopic", + 3: "GetChannelCount", + 4: "GetSubscriberGroups", + 5: "EnsureChannelCount", + 6: "EnsureSubscriberGroup", + 7: "DestroySubscriberGroup", + 8: "GetRemainingMessages", + 9: "GetTails", + 10: "EnsurePublisher", + 11: "DestroyPublisher", + 12: "Publish", + 13: "EnsureSubscriber", + 14: "DestroySubscriber", + 15: "InitializeSubscription", + 16: "EnsureSubscription", + 17: "GetSubscriberHeads", + 18: "GetLastCommited", + 19: "GetOwnedChannels", + 20: "SubscriberHeartbeat", + 21: "IsPositionCommitted", + 22: "PeekAtPosition", + 23: "Receive", + 24: "SeekSubscriber", + 25: "CommitPosition", + } + TopicServiceRequestType_value = map[string]int32{ + "RequestUnknown": 0, + "EnsureTopic": 1, + "DestroyTopic": 2, + "GetChannelCount": 3, + "GetSubscriberGroups": 4, + "EnsureChannelCount": 5, + "EnsureSubscriberGroup": 6, + "DestroySubscriberGroup": 7, + "GetRemainingMessages": 8, + "GetTails": 9, + "EnsurePublisher": 10, + "DestroyPublisher": 11, + "Publish": 12, + "EnsureSubscriber": 13, + "DestroySubscriber": 14, + "InitializeSubscription": 15, + "EnsureSubscription": 16, + "GetSubscriberHeads": 17, + "GetLastCommited": 18, + "GetOwnedChannels": 19, + "SubscriberHeartbeat": 20, + "IsPositionCommitted": 21, + "PeekAtPosition": 22, + "Receive": 23, + "SeekSubscriber": 24, + "CommitPosition": 25, + } +) + +func (x TopicServiceRequestType) Enum() *TopicServiceRequestType { + p := new(TopicServiceRequestType) + *p = x + return p +} + +func (x TopicServiceRequestType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TopicServiceRequestType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[0].Descriptor() +} + +func (TopicServiceRequestType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[0] +} + +func (x TopicServiceRequestType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TopicServiceRequestType.Descriptor instead. +func (TopicServiceRequestType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{0} +} + +// An enum representing different types of response. +// +// NOTE: The index numbers for the enum elements MUST NOT BE CHANGED as +// that would break backwards compatibility. Only new index numbers can +// be added. +type ResponseType int32 + +const ( + // The response is a message. + ResponseType_Message ResponseType = 0 + // The response is a map event. + ResponseType_Event ResponseType = 1 +) + +// Enum value maps for ResponseType. +var ( + ResponseType_name = map[int32]string{ + 0: "Message", + 1: "Event", + } + ResponseType_value = map[string]int32{ + "Message": 0, + "Event": 1, + } +) + +func (x ResponseType) Enum() *ResponseType { + p := new(ResponseType) + *p = x + return p +} + +func (x ResponseType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResponseType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[1].Descriptor() +} + +func (ResponseType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[1] +} + +func (x ResponseType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResponseType.Descriptor instead. +func (ResponseType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{1} +} + +type TopicEventType int32 + +const ( + // An unknown type. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + TopicEventType_EventUnknown TopicEventType = 0 + // The topic has been destroyed + TopicEventType_TopicDestroyed TopicEventType = 1 +) + +// Enum value maps for TopicEventType. +var ( + TopicEventType_name = map[int32]string{ + 0: "EventUnknown", + 1: "TopicDestroyed", + } + TopicEventType_value = map[string]int32{ + "EventUnknown": 0, + "TopicDestroyed": 1, + } +) + +func (x TopicEventType) Enum() *TopicEventType { + p := new(TopicEventType) + *p = x + return p +} + +func (x TopicEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TopicEventType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[2].Descriptor() +} + +func (TopicEventType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[2] +} + +func (x TopicEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TopicEventType.Descriptor instead. +func (TopicEventType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{2} +} + +// The types for a publisher event. +type PublisherEventType int32 + +const ( + // An unknown type. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + PublisherEventType_PublisherEventUnknown PublisherEventType = 0 + // The publisher has connected. + PublisherEventType_PublisherConnected PublisherEventType = 1 + // The publisher has disconnected. + PublisherEventType_PublisherDisconnected PublisherEventType = 2 + // A previously full topic now has space + PublisherEventType_PublisherChannelsFreed PublisherEventType = 3 + // The topic the publisher publishes to has been destroyed. + PublisherEventType_PublisherDestroyed PublisherEventType = 4 + // The topic the publisher publishes to has been released. + PublisherEventType_PublisherReleased PublisherEventType = 5 +) + +// Enum value maps for PublisherEventType. +var ( + PublisherEventType_name = map[int32]string{ + 0: "PublisherEventUnknown", + 1: "PublisherConnected", + 2: "PublisherDisconnected", + 3: "PublisherChannelsFreed", + 4: "PublisherDestroyed", + 5: "PublisherReleased", + } + PublisherEventType_value = map[string]int32{ + "PublisherEventUnknown": 0, + "PublisherConnected": 1, + "PublisherDisconnected": 2, + "PublisherChannelsFreed": 3, + "PublisherDestroyed": 4, + "PublisherReleased": 5, + } +) + +func (x PublisherEventType) Enum() *PublisherEventType { + p := new(PublisherEventType) + *p = x + return p +} + +func (x PublisherEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PublisherEventType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[3].Descriptor() +} + +func (PublisherEventType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[3] +} + +func (x PublisherEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PublisherEventType.Descriptor instead. +func (PublisherEventType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{3} +} + +type PublishStatus int32 + +const ( + // The offer invocation was successful and all elements were + // accepted into the page. + PublishStatus_Success PublishStatus = 0 + // The offer invocation was unsuccessful as the topic was full. + // The offer may have been partially successful if multiple elements + // had been offered. + // The publisher should wait for a PublisherEvent of type + PublishStatus_TopicFull PublishStatus = 1 +) + +// Enum value maps for PublishStatus. +var ( + PublishStatus_name = map[int32]string{ + 0: "Success", + 1: "TopicFull", + } + PublishStatus_value = map[string]int32{ + "Success": 0, + "TopicFull": 1, + } +) + +func (x PublishStatus) Enum() *PublishStatus { + p := new(PublishStatus) + *p = x + return p +} + +func (x PublishStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PublishStatus) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[4].Descriptor() +} + +func (PublishStatus) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[4] +} + +func (x PublishStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PublishStatus.Descriptor instead. +func (PublishStatus) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +type SubscriberEventType int32 + +const ( + // An unknown type. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + SubscriberEventType_SubscriberEventUnknown SubscriberEventType = 0 + // The event indicates the subscriber group was destroyed. + SubscriberEventType_SubscriberGroupDestroyed SubscriberEventType = 1 + // The event is a channel allocation event. + SubscriberEventType_SubscriberChannelAllocation SubscriberEventType = 2 + // The event is a channels lost event. + SubscriberEventType_SubscriberChannelsLost SubscriberEventType = 3 + // The event is a channel populated event. + SubscriberEventType_SubscriberChannelPopulated SubscriberEventType = 4 + // The head position of a channel has changed + SubscriberEventType_SubscriberChannelHead SubscriberEventType = 5 + // The event is an unsubscribed event. + SubscriberEventType_SubscriberUnsubscribed SubscriberEventType = 6 + // The parent topic was destroyed. + SubscriberEventType_SubscriberDestroyed SubscriberEventType = 7 + // The parent topic was released. + SubscriberEventType_SubscriberReleased SubscriberEventType = 8 + // The subscriber was disconnected. + SubscriberEventType_SubscriberDisconnected SubscriberEventType = 9 +) + +// Enum value maps for SubscriberEventType. +var ( + SubscriberEventType_name = map[int32]string{ + 0: "SubscriberEventUnknown", + 1: "SubscriberGroupDestroyed", + 2: "SubscriberChannelAllocation", + 3: "SubscriberChannelsLost", + 4: "SubscriberChannelPopulated", + 5: "SubscriberChannelHead", + 6: "SubscriberUnsubscribed", + 7: "SubscriberDestroyed", + 8: "SubscriberReleased", + 9: "SubscriberDisconnected", + } + SubscriberEventType_value = map[string]int32{ + "SubscriberEventUnknown": 0, + "SubscriberGroupDestroyed": 1, + "SubscriberChannelAllocation": 2, + "SubscriberChannelsLost": 3, + "SubscriberChannelPopulated": 4, + "SubscriberChannelHead": 5, + "SubscriberUnsubscribed": 6, + "SubscriberDestroyed": 7, + "SubscriberReleased": 8, + "SubscriberDisconnected": 9, + } +) + +func (x SubscriberEventType) Enum() *SubscriberEventType { + p := new(SubscriberEventType) + *p = x + return p +} + +func (x SubscriberEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SubscriberEventType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[5].Descriptor() +} + +func (SubscriberEventType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[5] +} + +func (x SubscriberEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SubscriberEventType.Descriptor instead. +func (SubscriberEventType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{5} +} + +// The status of a receive response. +type ReceiveStatus int32 + +const ( + // The receive request was successful. + ReceiveStatus_ReceiveSuccess ReceiveStatus = 0 + // The channel was exhausted. + ReceiveStatus_ChannelExhausted ReceiveStatus = 1 + // The channel was not allocated to the subscriber. + ReceiveStatus_ChannelNotAllocatedChannel ReceiveStatus = 2 + // The server did not recognise the subscriber + ReceiveStatus_UnknownSubscriber ReceiveStatus = 3 +) + +// Enum value maps for ReceiveStatus. +var ( + ReceiveStatus_name = map[int32]string{ + 0: "ReceiveSuccess", + 1: "ChannelExhausted", + 2: "ChannelNotAllocatedChannel", + 3: "UnknownSubscriber", + } + ReceiveStatus_value = map[string]int32{ + "ReceiveSuccess": 0, + "ChannelExhausted": 1, + "ChannelNotAllocatedChannel": 2, + "UnknownSubscriber": 3, + } +) + +func (x ReceiveStatus) Enum() *ReceiveStatus { + p := new(ReceiveStatus) + *p = x + return p +} + +func (x ReceiveStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ReceiveStatus) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[6].Descriptor() +} + +func (ReceiveStatus) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[6] +} + +func (x ReceiveStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ReceiveStatus.Descriptor instead. +func (ReceiveStatus) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{6} +} + +// The different result statuses for a commit request. +type CommitResponseStatus int32 + +const ( + // The position was successfully committed. + CommitResponseStatus_Committed CommitResponseStatus = 0 + // The position was already committed. + // Typically, this is caused by a commit of a higher position in the channel + // already being processed. + CommitResponseStatus_AlreadyCommitted CommitResponseStatus = 1 + // The commit request was rejected. + CommitResponseStatus_Rejected CommitResponseStatus = 2 + // The position was successfully committed but the committing subscriber + // does not own the committed channel. + CommitResponseStatus_Unowned CommitResponseStatus = 3 + // A commit request was made, but there was no position to be committed. + CommitResponseStatus_NothingToCommit CommitResponseStatus = 4 +) + +// Enum value maps for CommitResponseStatus. +var ( + CommitResponseStatus_name = map[int32]string{ + 0: "Committed", + 1: "AlreadyCommitted", + 2: "Rejected", + 3: "Unowned", + 4: "NothingToCommit", + } + CommitResponseStatus_value = map[string]int32{ + "Committed": 0, + "AlreadyCommitted": 1, + "Rejected": 2, + "Unowned": 3, + "NothingToCommit": 4, + } +) + +func (x CommitResponseStatus) Enum() *CommitResponseStatus { + p := new(CommitResponseStatus) + *p = x + return p +} + +func (x CommitResponseStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CommitResponseStatus) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[7].Descriptor() +} + +func (CommitResponseStatus) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[7] +} + +func (x CommitResponseStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CommitResponseStatus.Descriptor instead. +func (CommitResponseStatus) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{7} +} + +// A request to perform an operation on a remote NamedTopic. +type TopicServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the request + Type TopicServiceRequestType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.TopicServiceRequestType" json:"type,omitempty"` + // The topic identifier for the request. + // The identifier must be the same value returned by the initial ensure request. + // This is optional only for EnsureTopic as this cannot have a topic identifier + ProxyId *int32 `protobuf:"varint,2,opt,name=proxyId,proto3,oneof" json:"proxyId,omitempty"` + // The actual request message, this is optional because some messages do not require + // a message body, for example topic.size() + // The actual request message should be packed inside an Any message and set in this field. + // The proxy will know which message type to expect here based on the "type" field's value. + Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3,oneof" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicServiceRequest) Reset() { + *x = TopicServiceRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicServiceRequest) ProtoMessage() {} + +func (x *TopicServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicServiceRequest.ProtoReflect.Descriptor instead. +func (*TopicServiceRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{0} +} + +func (x *TopicServiceRequest) GetType() TopicServiceRequestType { + if x != nil { + return x.Type + } + return TopicServiceRequestType_RequestUnknown +} + +func (x *TopicServiceRequest) GetProxyId() int32 { + if x != nil && x.ProxyId != nil { + return *x.ProxyId + } + return 0 +} + +func (x *TopicServiceRequest) GetMessage() *anypb.Any { + if x != nil { + return x.Message + } + return nil +} + +// A response message from a Named Topic Service proxy. +// +// NOTE: If you add a new request message to this message the protocol +// version in com.oracle.coherence.grpc.NamedTopicProtocol must be +// increased. This only needs to be done once for any given Coherence +// release. +type TopicServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The proxy identifier for the response + ProxyId int32 `protobuf:"varint,1,opt,name=proxyId,proto3" json:"proxyId,omitempty"` + // An enum representing different response types. + // The type of the request. + Type ResponseType `protobuf:"varint,2,opt,name=type,proto3,enum=coherence.topic.v1.ResponseType" json:"type,omitempty"` + // The response can contain one of a number of response types + // The sender of the corresponding request should know which + // response type it expects + Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3,oneof" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicServiceResponse) Reset() { + *x = TopicServiceResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicServiceResponse) ProtoMessage() {} + +func (x *TopicServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicServiceResponse.ProtoReflect.Descriptor instead. +func (*TopicServiceResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{1} +} + +func (x *TopicServiceResponse) GetProxyId() int32 { + if x != nil { + return x.ProxyId + } + return 0 +} + +func (x *TopicServiceResponse) GetType() ResponseType { + if x != nil { + return x.Type + } + return ResponseType_Message +} + +func (x *TopicServiceResponse) GetMessage() *anypb.Any { + if x != nil { + return x.Message + } + return nil +} + +// A request to ensure a specific topic. +type EnsureTopicRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureTopicRequest) Reset() { + *x = EnsureTopicRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureTopicRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureTopicRequest) ProtoMessage() {} + +func (x *EnsureTopicRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureTopicRequest.ProtoReflect.Descriptor instead. +func (*EnsureTopicRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{2} +} + +func (x *EnsureTopicRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +// An event to indicate the state of a NamedTopic has changed. +type NamedTopicEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the event. + Type TopicEventType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.TopicEventType" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NamedTopicEvent) Reset() { + *x = NamedTopicEvent{} + mi := &file_topic_service_messages_v1_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NamedTopicEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NamedTopicEvent) ProtoMessage() {} + +func (x *NamedTopicEvent) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NamedTopicEvent.ProtoReflect.Descriptor instead. +func (*NamedTopicEvent) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{3} +} + +func (x *NamedTopicEvent) GetType() TopicEventType { + if x != nil { + return x.Type + } + return TopicEventType_EventUnknown +} + +// Ensure that a topic has a specified number of channels. +type EnsureChannelCountRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the name of the topic if this message is sent without a topic id + Topic *string `protobuf:"bytes,1,opt,name=topic,proto3,oneof" json:"topic,omitempty"` + // the required number of channels + RequiredCount int32 `protobuf:"varint,2,opt,name=requiredCount,proto3" json:"requiredCount,omitempty"` + // the number of channels to create if the actual count is less + // than the requiredCount + ChannelCount *int32 `protobuf:"varint,3,opt,name=channelCount,proto3,oneof" json:"channelCount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureChannelCountRequest) Reset() { + *x = EnsureChannelCountRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureChannelCountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureChannelCountRequest) ProtoMessage() {} + +func (x *EnsureChannelCountRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureChannelCountRequest.ProtoReflect.Descriptor instead. +func (*EnsureChannelCountRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +func (x *EnsureChannelCountRequest) GetTopic() string { + if x != nil && x.Topic != nil { + return *x.Topic + } + return "" +} + +func (x *EnsureChannelCountRequest) GetRequiredCount() int32 { + if x != nil { + return x.RequiredCount + } + return 0 +} + +func (x *EnsureChannelCountRequest) GetChannelCount() int32 { + if x != nil && x.ChannelCount != nil { + return *x.ChannelCount + } + return 0 +} + +// A request to ensure a subscriber group exists for a topic. +type EnsureSubscriberGroupRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the name of the subscriber group + SubscriberGroup string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3" json:"subscriberGroup,omitempty"` + // an optional Filter to filter received messages + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // an optional ValueExtractor to convert received messages + Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriberGroupRequest) Reset() { + *x = EnsureSubscriberGroupRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriberGroupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriberGroupRequest) ProtoMessage() {} + +func (x *EnsureSubscriberGroupRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriberGroupRequest.ProtoReflect.Descriptor instead. +func (*EnsureSubscriberGroupRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{5} +} + +func (x *EnsureSubscriberGroupRequest) GetSubscriberGroup() string { + if x != nil { + return x.SubscriberGroup + } + return "" +} + +func (x *EnsureSubscriberGroupRequest) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +func (x *EnsureSubscriberGroupRequest) GetExtractor() []byte { + if x != nil { + return x.Extractor + } + return nil +} + +// Get a count of the remaining messages in a topic for a subscriber group. +type GetRemainingMessagesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the name of the topic if this message is sent without a topic id + Topic *string `protobuf:"bytes,1,opt,name=topic,proto3,oneof" json:"topic,omitempty"` + // The subscriber group to obtain the remaining message counts for + SubscriberGroup string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3" json:"subscriberGroup,omitempty"` + // The channels to obtain the remaining message count for. + // An empty channel set will return all channels. + Channels []int32 `protobuf:"varint,3,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRemainingMessagesRequest) Reset() { + *x = GetRemainingMessagesRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRemainingMessagesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRemainingMessagesRequest) ProtoMessage() {} + +func (x *GetRemainingMessagesRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRemainingMessagesRequest.ProtoReflect.Descriptor instead. +func (*GetRemainingMessagesRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{6} +} + +func (x *GetRemainingMessagesRequest) GetTopic() string { + if x != nil && x.Topic != nil { + return *x.Topic + } + return "" +} + +func (x *GetRemainingMessagesRequest) GetSubscriberGroup() string { + if x != nil { + return x.SubscriberGroup + } + return "" +} + +func (x *GetRemainingMessagesRequest) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// A request to ensure a specific publisher. +type EnsurePublisherRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + // The number of channels the publisher requires + ChannelCount int32 `protobuf:"varint,2,opt,name=channelCount,proto3" json:"channelCount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsurePublisherRequest) Reset() { + *x = EnsurePublisherRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsurePublisherRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsurePublisherRequest) ProtoMessage() {} + +func (x *EnsurePublisherRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsurePublisherRequest.ProtoReflect.Descriptor instead. +func (*EnsurePublisherRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{7} +} + +func (x *EnsurePublisherRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *EnsurePublisherRequest) GetChannelCount() int32 { + if x != nil { + return x.ChannelCount + } + return 0 +} + +// The response message sent as a result of an EnsurePublisher request. +type EnsurePublisherResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The publisher proxy identifier + ProxyId int32 `protobuf:"varint,1,opt,name=proxyId,proto3" json:"proxyId,omitempty"` + // The identifier of the publisher + PublisherId int64 `protobuf:"varint,2,opt,name=publisherId,proto3" json:"publisherId,omitempty"` + // The number of channels the topic has. + ChannelCount int32 `protobuf:"varint,3,opt,name=channelCount,proto3" json:"channelCount,omitempty"` + // The maximum batch size before the publisher is throttled + MaxBatchSize int64 `protobuf:"varint,4,opt,name=maxBatchSize,proto3" json:"maxBatchSize,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsurePublisherResponse) Reset() { + *x = EnsurePublisherResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsurePublisherResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsurePublisherResponse) ProtoMessage() {} + +func (x *EnsurePublisherResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsurePublisherResponse.ProtoReflect.Descriptor instead. +func (*EnsurePublisherResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{8} +} + +func (x *EnsurePublisherResponse) GetProxyId() int32 { + if x != nil { + return x.ProxyId + } + return 0 +} + +func (x *EnsurePublisherResponse) GetPublisherId() int64 { + if x != nil { + return x.PublisherId + } + return 0 +} + +func (x *EnsurePublisherResponse) GetChannelCount() int32 { + if x != nil { + return x.ChannelCount + } + return 0 +} + +func (x *EnsurePublisherResponse) GetMaxBatchSize() int64 { + if x != nil { + return x.MaxBatchSize + } + return 0 +} + +// An event to indicate the state of a NamedTopic Publisher has changed. +type PublisherEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the event. + Type PublisherEventType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.PublisherEventType" json:"type,omitempty"` + // The channels the event relates to. + Channels []int32 `protobuf:"varint,2,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublisherEvent) Reset() { + *x = PublisherEvent{} + mi := &file_topic_service_messages_v1_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublisherEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublisherEvent) ProtoMessage() {} + +func (x *PublisherEvent) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublisherEvent.ProtoReflect.Descriptor instead. +func (*PublisherEvent) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{9} +} + +func (x *PublisherEvent) GetType() PublisherEventType { + if x != nil { + return x.Type + } + return PublisherEventType_PublisherEventUnknown +} + +func (x *PublisherEvent) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// A request to publish values to a channel in a topic +// using a previously ensured publisher. +type PublishRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel to publish to + Channel int32 `protobuf:"varint,2,opt,name=channel,proto3" json:"channel,omitempty"` + // The serialized values to publish. + Values [][]byte `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` + // The identifier used to register for notifications + // if the topic is full. + // This is used by the Coherence Java client and can be safely + // ignored in other implementations. + NotificationIdentifier *int32 `protobuf:"varint,4,opt,name=notificationIdentifier,proto3,oneof" json:"notificationIdentifier,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublishRequest) Reset() { + *x = PublishRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublishRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishRequest) ProtoMessage() {} + +func (x *PublishRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishRequest.ProtoReflect.Descriptor instead. +func (*PublishRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{10} +} + +func (x *PublishRequest) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *PublishRequest) GetValues() [][]byte { + if x != nil { + return x.Values + } + return nil +} + +func (x *PublishRequest) GetNotificationIdentifier() int32 { + if x != nil && x.NotificationIdentifier != nil { + return *x.NotificationIdentifier + } + return 0 +} + +// The status of a published value. +type PublishedValueStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to State: + // + // *PublishedValueStatus_Position + // *PublishedValueStatus_Error + State isPublishedValueStatus_State `protobuf_oneof:"state"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublishedValueStatus) Reset() { + *x = PublishedValueStatus{} + mi := &file_topic_service_messages_v1_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublishedValueStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishedValueStatus) ProtoMessage() {} + +func (x *PublishedValueStatus) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishedValueStatus.ProtoReflect.Descriptor instead. +func (*PublishedValueStatus) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{11} +} + +func (x *PublishedValueStatus) GetState() isPublishedValueStatus_State { + if x != nil { + return x.State + } + return nil +} + +func (x *PublishedValueStatus) GetPosition() *TopicPosition { + if x != nil { + if x, ok := x.State.(*PublishedValueStatus_Position); ok { + return x.Position + } + } + return nil +} + +func (x *PublishedValueStatus) GetError() *v1.ErrorMessage { + if x != nil { + if x, ok := x.State.(*PublishedValueStatus_Error); ok { + return x.Error + } + } + return nil +} + +type isPublishedValueStatus_State interface { + isPublishedValueStatus_State() +} + +type PublishedValueStatus_Position struct { + // An opaque representation of the position the element was published to + Position *TopicPosition `protobuf:"bytes,1,opt,name=position,proto3,oneof"` +} + +type PublishedValueStatus_Error struct { + // Any error that may have occurred, in which case the value was not published. + Error *v1.ErrorMessage `protobuf:"bytes,2,opt,name=error,proto3,oneof"` +} + +func (*PublishedValueStatus_Position) isPublishedValueStatus_State() {} + +func (*PublishedValueStatus_Error) isPublishedValueStatus_State() {} + +// The result of publishing values to a topic. +type PublishResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel published to. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The result status. + Status PublishStatus `protobuf:"varint,3,opt,name=status,proto3,enum=coherence.topic.v1.PublishStatus" json:"status,omitempty"` + // The number of values that were successfully published. + AcceptedCount int32 `protobuf:"varint,5,opt,name=acceptedCount,proto3" json:"acceptedCount,omitempty"` + // The remaining capacity. + RemainingCapacity int32 `protobuf:"varint,6,opt,name=remainingCapacity,proto3" json:"remainingCapacity,omitempty"` + // A status for each of the published values. + ValueStatus []*PublishedValueStatus `protobuf:"bytes,7,rep,name=valueStatus,proto3" json:"valueStatus,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublishResult) Reset() { + *x = PublishResult{} + mi := &file_topic_service_messages_v1_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublishResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishResult) ProtoMessage() {} + +func (x *PublishResult) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishResult.ProtoReflect.Descriptor instead. +func (*PublishResult) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{12} +} + +func (x *PublishResult) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *PublishResult) GetStatus() PublishStatus { + if x != nil { + return x.Status + } + return PublishStatus_Success +} + +func (x *PublishResult) GetAcceptedCount() int32 { + if x != nil { + return x.AcceptedCount + } + return 0 +} + +func (x *PublishResult) GetRemainingCapacity() int32 { + if x != nil { + return x.RemainingCapacity + } + return 0 +} + +func (x *PublishResult) GetValueStatus() []*PublishedValueStatus { + if x != nil { + return x.ValueStatus + } + return nil +} + +// A position within a paged topic. +type PagedPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The page identifier. + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + // The offset within a page + Offset int32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PagedPosition) Reset() { + *x = PagedPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PagedPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PagedPosition) ProtoMessage() {} + +func (x *PagedPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PagedPosition.ProtoReflect.Descriptor instead. +func (*PagedPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{13} +} + +func (x *PagedPosition) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *PagedPosition) GetOffset() int32 { + if x != nil { + return x.Offset + } + return 0 +} + +// An opaque topic position +type TopicPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Position *anypb.Any `protobuf:"bytes,1,opt,name=position,proto3" json:"position,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicPosition) Reset() { + *x = TopicPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicPosition) ProtoMessage() {} + +func (x *TopicPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicPosition.ProtoReflect.Descriptor instead. +func (*TopicPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{14} +} + +func (x *TopicPosition) GetPosition() *anypb.Any { + if x != nil { + return x.Position + } + return nil +} + +// The response to an EnsureSubscriberRequest +type EnsureSubscriberResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The publisher proxy identifier + ProxyId int32 `protobuf:"varint,1,opt,name=proxyId,proto3" json:"proxyId,omitempty"` + // The unique server side subscriber identifier. + SubscriberId *SubscriberId `protobuf:"bytes,2,opt,name=subscriberId,proto3" json:"subscriberId,omitempty"` + // The subscribers group identifier + GroupId *SubscriberGroupId `protobuf:"bytes,3,opt,name=groupId,proto3" json:"groupId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriberResponse) Reset() { + *x = EnsureSubscriberResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriberResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriberResponse) ProtoMessage() {} + +func (x *EnsureSubscriberResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriberResponse.ProtoReflect.Descriptor instead. +func (*EnsureSubscriberResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{15} +} + +func (x *EnsureSubscriberResponse) GetProxyId() int32 { + if x != nil { + return x.ProxyId + } + return 0 +} + +func (x *EnsureSubscriberResponse) GetSubscriberId() *SubscriberId { + if x != nil { + return x.SubscriberId + } + return nil +} + +func (x *EnsureSubscriberResponse) GetGroupId() *SubscriberGroupId { + if x != nil { + return x.GroupId + } + return nil +} + +// A request to ensure a specific subscriber. +type EnsureSubscriberRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + // the optional name of the subscriber group + SubscriberGroup *string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3,oneof" json:"subscriberGroup,omitempty"` + // an optional Filter to filter received messages + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // an optional ValueExtractor to convert received messages + Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` + // True to return an empty value if the topic is empty + CompleteOnEmpty bool `protobuf:"varint,5,opt,name=completeOnEmpty,proto3" json:"completeOnEmpty,omitempty"` + // The channels to allocate to this subscriber (invalid channels will be ignored) + Channels []int32 `protobuf:"varint,6,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriberRequest) Reset() { + *x = EnsureSubscriberRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriberRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriberRequest) ProtoMessage() {} + +func (x *EnsureSubscriberRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriberRequest.ProtoReflect.Descriptor instead. +func (*EnsureSubscriberRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{16} +} + +func (x *EnsureSubscriberRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *EnsureSubscriberRequest) GetSubscriberGroup() string { + if x != nil && x.SubscriberGroup != nil { + return *x.SubscriberGroup + } + return "" +} + +func (x *EnsureSubscriberRequest) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +func (x *EnsureSubscriberRequest) GetExtractor() []byte { + if x != nil { + return x.Extractor + } + return nil +} + +func (x *EnsureSubscriberRequest) GetCompleteOnEmpty() bool { + if x != nil { + return x.CompleteOnEmpty + } + return false +} + +func (x *EnsureSubscriberRequest) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// An event to indicate the state of a NamedTopic Subscriber has changed. +type SubscriberEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the event. + Type SubscriberEventType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.SubscriberEventType" json:"type,omitempty"` + // The channels associated with the event + Channels []int32 `protobuf:"varint,2,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriberEvent) Reset() { + *x = SubscriberEvent{} + mi := &file_topic_service_messages_v1_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriberEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriberEvent) ProtoMessage() {} + +func (x *SubscriberEvent) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriberEvent.ProtoReflect.Descriptor instead. +func (*SubscriberEvent) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{17} +} + +func (x *SubscriberEvent) GetType() SubscriberEventType { + if x != nil { + return x.Type + } + return SubscriberEventType_SubscriberEventUnknown +} + +func (x *SubscriberEvent) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// The unique identifier for a subscriber. +type SubscriberId struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The subscriber identifier. + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // The owning member UUID. + Uuid []byte `protobuf:"bytes,4,opt,name=uuid,proto3" json:"uuid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriberId) Reset() { + *x = SubscriberId{} + mi := &file_topic_service_messages_v1_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriberId) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriberId) ProtoMessage() {} + +func (x *SubscriberId) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriberId.ProtoReflect.Descriptor instead. +func (*SubscriberId) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{18} +} + +func (x *SubscriberId) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *SubscriberId) GetUuid() []byte { + if x != nil { + return x.Uuid + } + return nil +} + +// An identifier for a subscriber group +type SubscriberGroupId struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The subscriber group name + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The subscriber group identifier + Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriberGroupId) Reset() { + *x = SubscriberGroupId{} + mi := &file_topic_service_messages_v1_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriberGroupId) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriberGroupId) ProtoMessage() {} + +func (x *SubscriberGroupId) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriberGroupId.ProtoReflect.Descriptor instead. +func (*SubscriberGroupId) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{19} +} + +func (x *SubscriberGroupId) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SubscriberGroupId) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +// Initialize the subscriber connection. +type InitializeSubscriptionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // A flag to indicate if the subscriber was initially disconnected. + Disconnected bool `protobuf:"varint,1,opt,name=disconnected,proto3" json:"disconnected,omitempty"` + // This is a reconnection of an existing subscriber + Reconnect bool `protobuf:"varint,2,opt,name=reconnect,proto3" json:"reconnect,omitempty"` + // A flag to indicate that the reconnect logic should force a reconnect + // request even if the subscriber is in the config map + ForceReconnect bool `protobuf:"varint,3,opt,name=forceReconnect,proto3" json:"forceReconnect,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InitializeSubscriptionRequest) Reset() { + *x = InitializeSubscriptionRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InitializeSubscriptionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitializeSubscriptionRequest) ProtoMessage() {} + +func (x *InitializeSubscriptionRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InitializeSubscriptionRequest.ProtoReflect.Descriptor instead. +func (*InitializeSubscriptionRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{20} +} + +func (x *InitializeSubscriptionRequest) GetDisconnected() bool { + if x != nil { + return x.Disconnected + } + return false +} + +func (x *InitializeSubscriptionRequest) GetReconnect() bool { + if x != nil { + return x.Reconnect + } + return false +} + +func (x *InitializeSubscriptionRequest) GetForceReconnect() bool { + if x != nil { + return x.ForceReconnect + } + return false +} + +// A response to an initialize subscriber request. +type InitializeSubscriptionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The identifier for the subscribers current subscription. + SubscriptionId int64 `protobuf:"varint,1,opt,name=subscriptionId,proto3" json:"subscriptionId,omitempty"` + // The subscribers connection timestamp + Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // The head positions of the topic channels + Heads []*TopicPosition `protobuf:"bytes,3,rep,name=heads,proto3" json:"heads,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InitializeSubscriptionResponse) Reset() { + *x = InitializeSubscriptionResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InitializeSubscriptionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitializeSubscriptionResponse) ProtoMessage() {} + +func (x *InitializeSubscriptionResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InitializeSubscriptionResponse.ProtoReflect.Descriptor instead. +func (*InitializeSubscriptionResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{21} +} + +func (x *InitializeSubscriptionResponse) GetSubscriptionId() int64 { + if x != nil { + return x.SubscriptionId + } + return 0 +} + +func (x *InitializeSubscriptionResponse) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *InitializeSubscriptionResponse) GetHeads() []*TopicPosition { + if x != nil { + return x.Heads + } + return nil +} + +// Ensure a subscriber has a subscription in the topic. +type EnsureSubscriptionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The identifier for the subscribers current subscription. + SubscriptionId int64 `protobuf:"varint,1,opt,name=subscriptionId,proto3" json:"subscriptionId,omitempty"` + // A flag to indicate that the reconnect logic should force a reconnect + // request even if the subscriber is in the config map + ForceReconnect bool `protobuf:"varint,2,opt,name=forceReconnect,proto3" json:"forceReconnect,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriptionRequest) Reset() { + *x = EnsureSubscriptionRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriptionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriptionRequest) ProtoMessage() {} + +func (x *EnsureSubscriptionRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriptionRequest.ProtoReflect.Descriptor instead. +func (*EnsureSubscriptionRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{22} +} + +func (x *EnsureSubscriptionRequest) GetSubscriptionId() int64 { + if x != nil { + return x.SubscriptionId + } + return 0 +} + +func (x *EnsureSubscriptionRequest) GetForceReconnect() bool { + if x != nil { + return x.ForceReconnect + } + return false +} + +// An element received by a subscriber after calling receive. +type TopicElement struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel that the element was received from. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The serialized binary value + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // The position the element was in the topic + Position *TopicPosition `protobuf:"bytes,3,opt,name=position,proto3" json:"position,omitempty"` + // the timestamp the value was published + Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicElement) Reset() { + *x = TopicElement{} + mi := &file_topic_service_messages_v1_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicElement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicElement) ProtoMessage() {} + +func (x *TopicElement) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicElement.ProtoReflect.Descriptor instead. +func (*TopicElement) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{23} +} + +func (x *TopicElement) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *TopicElement) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *TopicElement) GetPosition() *TopicPosition { + if x != nil { + return x.Position + } + return nil +} + +func (x *TopicElement) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +type ReceiveRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel to received from. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The maximum number of messages to return. + // If not set (or <= 0) multiple messages may be returned, in which case + // the exact number will be determined by the topic implementation + // on the server. + MaxMessages *int32 `protobuf:"varint,2,opt,name=maxMessages,proto3,oneof" json:"maxMessages,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReceiveRequest) Reset() { + *x = ReceiveRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReceiveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiveRequest) ProtoMessage() {} + +func (x *ReceiveRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReceiveRequest.ProtoReflect.Descriptor instead. +func (*ReceiveRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{24} +} + +func (x *ReceiveRequest) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *ReceiveRequest) GetMaxMessages() int32 { + if x != nil && x.MaxMessages != nil { + return *x.MaxMessages + } + return 0 +} + +type ReceiveResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The status of the receive result. + Status ReceiveStatus `protobuf:"varint,1,opt,name=status,proto3,enum=coherence.topic.v1.ReceiveStatus" json:"status,omitempty"` + // The serialized values received from the channel. + Values [][]byte `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` + // The new head position for the channel. + HeadPosition *TopicPosition `protobuf:"bytes,3,opt,name=headPosition,proto3" json:"headPosition,omitempty"` + // The count of the remaining values. + RemainingValues int32 `protobuf:"varint,4,opt,name=remainingValues,proto3" json:"remainingValues,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReceiveResponse) Reset() { + *x = ReceiveResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReceiveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiveResponse) ProtoMessage() {} + +func (x *ReceiveResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReceiveResponse.ProtoReflect.Descriptor instead. +func (*ReceiveResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{25} +} + +func (x *ReceiveResponse) GetStatus() ReceiveStatus { + if x != nil { + return x.Status + } + return ReceiveStatus_ReceiveSuccess +} + +func (x *ReceiveResponse) GetValues() [][]byte { + if x != nil { + return x.Values + } + return nil +} + +func (x *ReceiveResponse) GetHeadPosition() *TopicPosition { + if x != nil { + return x.HeadPosition + } + return nil +} + +func (x *ReceiveResponse) GetRemainingValues() int32 { + if x != nil { + return x.RemainingValues + } + return 0 +} + +// A request to seek (reposition) one or more channels. +type SeekRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The positions to seek to. + // + // Types that are valid to be assigned to Positions: + // + // *SeekRequest_ByPosition + // *SeekRequest_ByTimestamp + Positions isSeekRequest_Positions `protobuf_oneof:"positions"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SeekRequest) Reset() { + *x = SeekRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SeekRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SeekRequest) ProtoMessage() {} + +func (x *SeekRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SeekRequest.ProtoReflect.Descriptor instead. +func (*SeekRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{26} +} + +func (x *SeekRequest) GetPositions() isSeekRequest_Positions { + if x != nil { + return x.Positions + } + return nil +} + +func (x *SeekRequest) GetByPosition() *MapOfChannelAndPosition { + if x != nil { + if x, ok := x.Positions.(*SeekRequest_ByPosition); ok { + return x.ByPosition + } + } + return nil +} + +func (x *SeekRequest) GetByTimestamp() *MapOfChannelAndTimestamp { + if x != nil { + if x, ok := x.Positions.(*SeekRequest_ByTimestamp); ok { + return x.ByTimestamp + } + } + return nil +} + +type isSeekRequest_Positions interface { + isSeekRequest_Positions() +} + +type SeekRequest_ByPosition struct { + // Seek to the specified positions in channels. + ByPosition *MapOfChannelAndPosition `protobuf:"bytes,1,opt,name=byPosition,proto3,oneof"` +} + +type SeekRequest_ByTimestamp struct { + // Seek to the specified timestamps in channels. + ByTimestamp *MapOfChannelAndTimestamp `protobuf:"bytes,2,opt,name=byTimestamp,proto3,oneof"` +} + +func (*SeekRequest_ByPosition) isSeekRequest_Positions() {} + +func (*SeekRequest_ByTimestamp) isSeekRequest_Positions() {} + +// The result of a seek request for a channel. +type SeekedPositions struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The new head position. + Head *TopicPosition `protobuf:"bytes,2,opt,name=head,proto3" json:"head,omitempty"` + // The seeked to position. + SeekedTo *TopicPosition `protobuf:"bytes,3,opt,name=seekedTo,proto3" json:"seekedTo,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SeekedPositions) Reset() { + *x = SeekedPositions{} + mi := &file_topic_service_messages_v1_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SeekedPositions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SeekedPositions) ProtoMessage() {} + +func (x *SeekedPositions) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SeekedPositions.ProtoReflect.Descriptor instead. +func (*SeekedPositions) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{27} +} + +func (x *SeekedPositions) GetHead() *TopicPosition { + if x != nil { + return x.Head + } + return nil +} + +func (x *SeekedPositions) GetSeekedTo() *TopicPosition { + if x != nil { + return x.SeekedTo + } + return nil +} + +// The result of a seek request. +type SeekResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The map of SeekedPositions by channel. + Positions map[int32]*SeekedPositions `protobuf:"bytes,1,rep,name=positions,proto3" json:"positions,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SeekResponse) Reset() { + *x = SeekResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SeekResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SeekResponse) ProtoMessage() {} + +func (x *SeekResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SeekResponse.ProtoReflect.Descriptor instead. +func (*SeekResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{28} +} + +func (x *SeekResponse) GetPositions() map[int32]*SeekedPositions { + if x != nil { + return x.Positions + } + return nil +} + +// The result of a commit request +type CommitResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel committed. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The position committed. + Position *TopicPosition `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + // The channel's head position. + Head *TopicPosition `protobuf:"bytes,3,opt,name=head,proto3" json:"head,omitempty"` + // The status of the commit response. + Status CommitResponseStatus `protobuf:"varint,4,opt,name=status,proto3,enum=coherence.topic.v1.CommitResponseStatus" json:"status,omitempty"` + // Any error that may hav occurred + Error *v1.ErrorMessage `protobuf:"bytes,5,opt,name=error,proto3,oneof" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CommitResponse) Reset() { + *x = CommitResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommitResponse) ProtoMessage() {} + +func (x *CommitResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommitResponse.ProtoReflect.Descriptor instead. +func (*CommitResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{29} +} + +func (x *CommitResponse) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *CommitResponse) GetPosition() *TopicPosition { + if x != nil { + return x.Position + } + return nil +} + +func (x *CommitResponse) GetHead() *TopicPosition { + if x != nil { + return x.Head + } + return nil +} + +func (x *CommitResponse) GetStatus() CommitResponseStatus { + if x != nil { + return x.Status + } + return CommitResponseStatus_Committed +} + +func (x *CommitResponse) GetError() *v1.ErrorMessage { + if x != nil { + return x.Error + } + return nil +} + +// A channel and position. +type ChannelAndPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + Position *TopicPosition `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChannelAndPosition) Reset() { + *x = ChannelAndPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChannelAndPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChannelAndPosition) ProtoMessage() {} + +func (x *ChannelAndPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChannelAndPosition.ProtoReflect.Descriptor instead. +func (*ChannelAndPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{30} +} + +func (x *ChannelAndPosition) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *ChannelAndPosition) GetPosition() *TopicPosition { + if x != nil { + return x.Position + } + return nil +} + +// A map of topic channel identifier to position. +type MapOfChannelAndPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The map of channels to positions. + Positions map[int32]*TopicPosition `protobuf:"bytes,1,rep,name=positions,proto3" json:"positions,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MapOfChannelAndPosition) Reset() { + *x = MapOfChannelAndPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MapOfChannelAndPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MapOfChannelAndPosition) ProtoMessage() {} + +func (x *MapOfChannelAndPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MapOfChannelAndPosition.ProtoReflect.Descriptor instead. +func (*MapOfChannelAndPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{31} +} + +func (x *MapOfChannelAndPosition) GetPositions() map[int32]*TopicPosition { + if x != nil { + return x.Positions + } + return nil +} + +// A map of topic channel identifier to timestamps. +type MapOfChannelAndTimestamp struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The map of channels to timestamps. + Timestamps map[int32]*timestamppb.Timestamp `protobuf:"bytes,1,rep,name=timestamps,proto3" json:"timestamps,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MapOfChannelAndTimestamp) Reset() { + *x = MapOfChannelAndTimestamp{} + mi := &file_topic_service_messages_v1_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MapOfChannelAndTimestamp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MapOfChannelAndTimestamp) ProtoMessage() {} + +func (x *MapOfChannelAndTimestamp) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MapOfChannelAndTimestamp.ProtoReflect.Descriptor instead. +func (*MapOfChannelAndTimestamp) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{32} +} + +func (x *MapOfChannelAndTimestamp) GetTimestamps() map[int32]*timestamppb.Timestamp { + if x != nil { + return x.Timestamps + } + return nil +} + +var File_topic_service_messages_v1_proto protoreflect.FileDescriptor + +const file_topic_service_messages_v1_proto_rawDesc = "" + + "\n" + + "\x1ftopic_service_messages_v1.proto\x12\x12coherence.topic.v1\x1a\x18common_messages_v1.proto\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xc2\x01\n" + + "\x13TopicServiceRequest\x12?\n" + + "\x04type\x18\x01 \x01(\x0e2+.coherence.topic.v1.TopicServiceRequestTypeR\x04type\x12\x1d\n" + + "\aproxyId\x18\x02 \x01(\x05H\x00R\aproxyId\x88\x01\x01\x123\n" + + "\amessage\x18\x03 \x01(\v2\x14.google.protobuf.AnyH\x01R\amessage\x88\x01\x01B\n" + + "\n" + + "\b_proxyIdB\n" + + "\n" + + "\b_message\"\xa7\x01\n" + + "\x14TopicServiceResponse\x12\x18\n" + + "\aproxyId\x18\x01 \x01(\x05R\aproxyId\x124\n" + + "\x04type\x18\x02 \x01(\x0e2 .coherence.topic.v1.ResponseTypeR\x04type\x123\n" + + "\amessage\x18\x03 \x01(\v2\x14.google.protobuf.AnyH\x00R\amessage\x88\x01\x01B\n" + + "\n" + + "\b_message\"*\n" + + "\x12EnsureTopicRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\"I\n" + + "\x0fNamedTopicEvent\x126\n" + + "\x04type\x18\x01 \x01(\x0e2\".coherence.topic.v1.TopicEventTypeR\x04type\"\xa0\x01\n" + + "\x19EnsureChannelCountRequest\x12\x19\n" + + "\x05topic\x18\x01 \x01(\tH\x00R\x05topic\x88\x01\x01\x12$\n" + + "\rrequiredCount\x18\x02 \x01(\x05R\rrequiredCount\x12'\n" + + "\fchannelCount\x18\x03 \x01(\x05H\x01R\fchannelCount\x88\x01\x01B\b\n" + + "\x06_topicB\x0f\n" + + "\r_channelCount\"\xa1\x01\n" + + "\x1cEnsureSubscriberGroupRequest\x12(\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tR\x0fsubscriberGroup\x12\x1b\n" + + "\x06filter\x18\x03 \x01(\fH\x00R\x06filter\x88\x01\x01\x12!\n" + + "\textractor\x18\x04 \x01(\fH\x01R\textractor\x88\x01\x01B\t\n" + + "\a_filterB\f\n" + + "\n" + + "_extractor\"\x88\x01\n" + + "\x1bGetRemainingMessagesRequest\x12\x19\n" + + "\x05topic\x18\x01 \x01(\tH\x00R\x05topic\x88\x01\x01\x12(\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tR\x0fsubscriberGroup\x12\x1a\n" + + "\bchannels\x18\x03 \x03(\x05R\bchannelsB\b\n" + + "\x06_topic\"R\n" + + "\x16EnsurePublisherRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\x12\"\n" + + "\fchannelCount\x18\x02 \x01(\x05R\fchannelCount\"\x9d\x01\n" + + "\x17EnsurePublisherResponse\x12\x18\n" + + "\aproxyId\x18\x01 \x01(\x05R\aproxyId\x12 \n" + + "\vpublisherId\x18\x02 \x01(\x03R\vpublisherId\x12\"\n" + + "\fchannelCount\x18\x03 \x01(\x05R\fchannelCount\x12\"\n" + + "\fmaxBatchSize\x18\x04 \x01(\x03R\fmaxBatchSize\"l\n" + + "\x0ePublisherEvent\x12:\n" + + "\x04type\x18\x01 \x01(\x0e2&.coherence.topic.v1.PublisherEventTypeR\x04type\x12\x1e\n" + + "\bchannels\x18\x02 \x03(\x05B\x02\x10\x01R\bchannels\"\x9a\x01\n" + + "\x0ePublishRequest\x12\x18\n" + + "\achannel\x18\x02 \x01(\x05R\achannel\x12\x16\n" + + "\x06values\x18\x03 \x03(\fR\x06values\x12;\n" + + "\x16notificationIdentifier\x18\x04 \x01(\x05H\x00R\x16notificationIdentifier\x88\x01\x01B\x19\n" + + "\x17_notificationIdentifier\"\x9b\x01\n" + + "\x14PublishedValueStatus\x12?\n" + + "\bposition\x18\x01 \x01(\v2!.coherence.topic.v1.TopicPositionH\x00R\bposition\x129\n" + + "\x05error\x18\x02 \x01(\v2!.coherence.common.v1.ErrorMessageH\x00R\x05errorB\a\n" + + "\x05state\"\x84\x02\n" + + "\rPublishResult\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x129\n" + + "\x06status\x18\x03 \x01(\x0e2!.coherence.topic.v1.PublishStatusR\x06status\x12$\n" + + "\racceptedCount\x18\x05 \x01(\x05R\racceptedCount\x12,\n" + + "\x11remainingCapacity\x18\x06 \x01(\x05R\x11remainingCapacity\x12J\n" + + "\vvalueStatus\x18\a \x03(\v2(.coherence.topic.v1.PublishedValueStatusR\vvalueStatus\";\n" + + "\rPagedPosition\x12\x12\n" + + "\x04page\x18\x01 \x01(\x03R\x04page\x12\x16\n" + + "\x06offset\x18\x02 \x01(\x05R\x06offset\"A\n" + + "\rTopicPosition\x120\n" + + "\bposition\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\bposition\"\xbb\x01\n" + + "\x18EnsureSubscriberResponse\x12\x18\n" + + "\aproxyId\x18\x01 \x01(\x05R\aproxyId\x12D\n" + + "\fsubscriberId\x18\x02 \x01(\v2 .coherence.topic.v1.SubscriberIdR\fsubscriberId\x12?\n" + + "\agroupId\x18\x03 \x01(\v2%.coherence.topic.v1.SubscriberGroupIdR\agroupId\"\x91\x02\n" + + "\x17EnsureSubscriberRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\x12-\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tH\x00R\x0fsubscriberGroup\x88\x01\x01\x12\x1b\n" + + "\x06filter\x18\x03 \x01(\fH\x01R\x06filter\x88\x01\x01\x12!\n" + + "\textractor\x18\x04 \x01(\fH\x02R\textractor\x88\x01\x01\x12(\n" + + "\x0fcompleteOnEmpty\x18\x05 \x01(\bR\x0fcompleteOnEmpty\x12\x1a\n" + + "\bchannels\x18\x06 \x03(\x05R\bchannelsB\x12\n" + + "\x10_subscriberGroupB\t\n" + + "\a_filterB\f\n" + + "\n" + + "_extractor\"n\n" + + "\x0fSubscriberEvent\x12;\n" + + "\x04type\x18\x01 \x01(\x0e2'.coherence.topic.v1.SubscriberEventTypeR\x04type\x12\x1e\n" + + "\bchannels\x18\x02 \x03(\x05B\x02\x10\x01R\bchannels\"2\n" + + "\fSubscriberId\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + + "\x04uuid\x18\x04 \x01(\fR\x04uuid\"7\n" + + "\x11SubscriberGroupId\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x0e\n" + + "\x02id\x18\x02 \x01(\x03R\x02id\"\x89\x01\n" + + "\x1dInitializeSubscriptionRequest\x12\"\n" + + "\fdisconnected\x18\x01 \x01(\bR\fdisconnected\x12\x1c\n" + + "\treconnect\x18\x02 \x01(\bR\treconnect\x12&\n" + + "\x0eforceReconnect\x18\x03 \x01(\bR\x0eforceReconnect\"\xbb\x01\n" + + "\x1eInitializeSubscriptionResponse\x12&\n" + + "\x0esubscriptionId\x18\x01 \x01(\x03R\x0esubscriptionId\x128\n" + + "\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x127\n" + + "\x05heads\x18\x03 \x03(\v2!.coherence.topic.v1.TopicPositionR\x05heads\"k\n" + + "\x19EnsureSubscriptionRequest\x12&\n" + + "\x0esubscriptionId\x18\x01 \x01(\x03R\x0esubscriptionId\x12&\n" + + "\x0eforceReconnect\x18\x02 \x01(\bR\x0eforceReconnect\"\xb7\x01\n" + + "\fTopicElement\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\x12=\n" + + "\bposition\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\bposition\x128\n" + + "\ttimestamp\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\"a\n" + + "\x0eReceiveRequest\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12%\n" + + "\vmaxMessages\x18\x02 \x01(\x05H\x00R\vmaxMessages\x88\x01\x01B\x0e\n" + + "\f_maxMessages\"\xd5\x01\n" + + "\x0fReceiveResponse\x129\n" + + "\x06status\x18\x01 \x01(\x0e2!.coherence.topic.v1.ReceiveStatusR\x06status\x12\x16\n" + + "\x06values\x18\x02 \x03(\fR\x06values\x12E\n" + + "\fheadPosition\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\fheadPosition\x12(\n" + + "\x0fremainingValues\x18\x04 \x01(\x05R\x0fremainingValues\"\xbb\x01\n" + + "\vSeekRequest\x12M\n" + + "\n" + + "byPosition\x18\x01 \x01(\v2+.coherence.topic.v1.MapOfChannelAndPositionH\x00R\n" + + "byPosition\x12P\n" + + "\vbyTimestamp\x18\x02 \x01(\v2,.coherence.topic.v1.MapOfChannelAndTimestampH\x00R\vbyTimestampB\v\n" + + "\tpositions\"\x87\x01\n" + + "\x0fSeekedPositions\x125\n" + + "\x04head\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\x04head\x12=\n" + + "\bseekedTo\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\bseekedTo\"\xc0\x01\n" + + "\fSeekResponse\x12M\n" + + "\tpositions\x18\x01 \x03(\v2/.coherence.topic.v1.SeekResponse.PositionsEntryR\tpositions\x1aa\n" + + "\x0ePositionsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x129\n" + + "\x05value\x18\x02 \x01(\v2#.coherence.topic.v1.SeekedPositionsR\x05value:\x028\x01\"\xaa\x02\n" + + "\x0eCommitResponse\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12=\n" + + "\bposition\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\bposition\x125\n" + + "\x04head\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\x04head\x12@\n" + + "\x06status\x18\x04 \x01(\x0e2(.coherence.topic.v1.CommitResponseStatusR\x06status\x12<\n" + + "\x05error\x18\x05 \x01(\v2!.coherence.common.v1.ErrorMessageH\x00R\x05error\x88\x01\x01B\b\n" + + "\x06_error\"m\n" + + "\x12ChannelAndPosition\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12=\n" + + "\bposition\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\bposition\"\xd4\x01\n" + + "\x17MapOfChannelAndPosition\x12X\n" + + "\tpositions\x18\x01 \x03(\v2:.coherence.topic.v1.MapOfChannelAndPosition.PositionsEntryR\tpositions\x1a_\n" + + "\x0ePositionsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x127\n" + + "\x05value\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\x05value:\x028\x01\"\xd3\x01\n" + + "\x18MapOfChannelAndTimestamp\x12\\\n" + + "\n" + + "timestamps\x18\x01 \x03(\v2<.coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntryR\n" + + "timestamps\x1aY\n" + + "\x0fTimestampsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x120\n" + + "\x05value\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05value:\x028\x01*\xcc\x04\n" + + "\x17TopicServiceRequestType\x12\x12\n" + + "\x0eRequestUnknown\x10\x00\x12\x0f\n" + + "\vEnsureTopic\x10\x01\x12\x10\n" + + "\fDestroyTopic\x10\x02\x12\x13\n" + + "\x0fGetChannelCount\x10\x03\x12\x17\n" + + "\x13GetSubscriberGroups\x10\x04\x12\x16\n" + + "\x12EnsureChannelCount\x10\x05\x12\x19\n" + + "\x15EnsureSubscriberGroup\x10\x06\x12\x1a\n" + + "\x16DestroySubscriberGroup\x10\a\x12\x18\n" + + "\x14GetRemainingMessages\x10\b\x12\f\n" + + "\bGetTails\x10\t\x12\x13\n" + + "\x0fEnsurePublisher\x10\n" + + "\x12\x14\n" + + "\x10DestroyPublisher\x10\v\x12\v\n" + + "\aPublish\x10\f\x12\x14\n" + + "\x10EnsureSubscriber\x10\r\x12\x15\n" + + "\x11DestroySubscriber\x10\x0e\x12\x1a\n" + + "\x16InitializeSubscription\x10\x0f\x12\x16\n" + + "\x12EnsureSubscription\x10\x10\x12\x16\n" + + "\x12GetSubscriberHeads\x10\x11\x12\x13\n" + + "\x0fGetLastCommited\x10\x12\x12\x14\n" + + "\x10GetOwnedChannels\x10\x13\x12\x17\n" + + "\x13SubscriberHeartbeat\x10\x14\x12\x17\n" + + "\x13IsPositionCommitted\x10\x15\x12\x12\n" + + "\x0ePeekAtPosition\x10\x16\x12\v\n" + + "\aReceive\x10\x17\x12\x12\n" + + "\x0eSeekSubscriber\x10\x18\x12\x12\n" + + "\x0eCommitPosition\x10\x19*&\n" + + "\fResponseType\x12\v\n" + + "\aMessage\x10\x00\x12\t\n" + + "\x05Event\x10\x01*6\n" + + "\x0eTopicEventType\x12\x10\n" + + "\fEventUnknown\x10\x00\x12\x12\n" + + "\x0eTopicDestroyed\x10\x01*\xad\x01\n" + + "\x12PublisherEventType\x12\x19\n" + + "\x15PublisherEventUnknown\x10\x00\x12\x16\n" + + "\x12PublisherConnected\x10\x01\x12\x19\n" + + "\x15PublisherDisconnected\x10\x02\x12\x1a\n" + + "\x16PublisherChannelsFreed\x10\x03\x12\x16\n" + + "\x12PublisherDestroyed\x10\x04\x12\x15\n" + + "\x11PublisherReleased\x10\x05*+\n" + + "\rPublishStatus\x12\v\n" + + "\aSuccess\x10\x00\x12\r\n" + + "\tTopicFull\x10\x01*\xb0\x02\n" + + "\x13SubscriberEventType\x12\x1a\n" + + "\x16SubscriberEventUnknown\x10\x00\x12\x1c\n" + + "\x18SubscriberGroupDestroyed\x10\x01\x12\x1f\n" + + "\x1bSubscriberChannelAllocation\x10\x02\x12\x1a\n" + + "\x16SubscriberChannelsLost\x10\x03\x12\x1e\n" + + "\x1aSubscriberChannelPopulated\x10\x04\x12\x19\n" + + "\x15SubscriberChannelHead\x10\x05\x12\x1a\n" + + "\x16SubscriberUnsubscribed\x10\x06\x12\x17\n" + + "\x13SubscriberDestroyed\x10\a\x12\x16\n" + + "\x12SubscriberReleased\x10\b\x12\x1a\n" + + "\x16SubscriberDisconnected\x10\t*p\n" + + "\rReceiveStatus\x12\x12\n" + + "\x0eReceiveSuccess\x10\x00\x12\x14\n" + + "\x10ChannelExhausted\x10\x01\x12\x1e\n" + + "\x1aChannelNotAllocatedChannel\x10\x02\x12\x15\n" + + "\x11UnknownSubscriber\x10\x03*k\n" + + "\x14CommitResponseStatus\x12\r\n" + + "\tCommitted\x10\x00\x12\x14\n" + + "\x10AlreadyCommitted\x10\x01\x12\f\n" + + "\bRejected\x10\x02\x12\v\n" + + "\aUnowned\x10\x03\x12\x13\n" + + "\x0fNothingToCommit\x10\x04Bf\n" + + "+com.oracle.coherence.grpc.messages.topic.v1P\x01Z5github.com/oracle/coherence-go-client/proto/v1/topicsb\x06proto3" + +var ( + file_topic_service_messages_v1_proto_rawDescOnce sync.Once + file_topic_service_messages_v1_proto_rawDescData []byte +) + +func file_topic_service_messages_v1_proto_rawDescGZIP() []byte { + file_topic_service_messages_v1_proto_rawDescOnce.Do(func() { + file_topic_service_messages_v1_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_topic_service_messages_v1_proto_rawDesc), len(file_topic_service_messages_v1_proto_rawDesc))) + }) + return file_topic_service_messages_v1_proto_rawDescData +} + +var file_topic_service_messages_v1_proto_enumTypes = make([]protoimpl.EnumInfo, 8) +var file_topic_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_topic_service_messages_v1_proto_goTypes = []any{ + (TopicServiceRequestType)(0), // 0: coherence.topic.v1.TopicServiceRequestType + (ResponseType)(0), // 1: coherence.topic.v1.ResponseType + (TopicEventType)(0), // 2: coherence.topic.v1.TopicEventType + (PublisherEventType)(0), // 3: coherence.topic.v1.PublisherEventType + (PublishStatus)(0), // 4: coherence.topic.v1.PublishStatus + (SubscriberEventType)(0), // 5: coherence.topic.v1.SubscriberEventType + (ReceiveStatus)(0), // 6: coherence.topic.v1.ReceiveStatus + (CommitResponseStatus)(0), // 7: coherence.topic.v1.CommitResponseStatus + (*TopicServiceRequest)(nil), // 8: coherence.topic.v1.TopicServiceRequest + (*TopicServiceResponse)(nil), // 9: coherence.topic.v1.TopicServiceResponse + (*EnsureTopicRequest)(nil), // 10: coherence.topic.v1.EnsureTopicRequest + (*NamedTopicEvent)(nil), // 11: coherence.topic.v1.NamedTopicEvent + (*EnsureChannelCountRequest)(nil), // 12: coherence.topic.v1.EnsureChannelCountRequest + (*EnsureSubscriberGroupRequest)(nil), // 13: coherence.topic.v1.EnsureSubscriberGroupRequest + (*GetRemainingMessagesRequest)(nil), // 14: coherence.topic.v1.GetRemainingMessagesRequest + (*EnsurePublisherRequest)(nil), // 15: coherence.topic.v1.EnsurePublisherRequest + (*EnsurePublisherResponse)(nil), // 16: coherence.topic.v1.EnsurePublisherResponse + (*PublisherEvent)(nil), // 17: coherence.topic.v1.PublisherEvent + (*PublishRequest)(nil), // 18: coherence.topic.v1.PublishRequest + (*PublishedValueStatus)(nil), // 19: coherence.topic.v1.PublishedValueStatus + (*PublishResult)(nil), // 20: coherence.topic.v1.PublishResult + (*PagedPosition)(nil), // 21: coherence.topic.v1.PagedPosition + (*TopicPosition)(nil), // 22: coherence.topic.v1.TopicPosition + (*EnsureSubscriberResponse)(nil), // 23: coherence.topic.v1.EnsureSubscriberResponse + (*EnsureSubscriberRequest)(nil), // 24: coherence.topic.v1.EnsureSubscriberRequest + (*SubscriberEvent)(nil), // 25: coherence.topic.v1.SubscriberEvent + (*SubscriberId)(nil), // 26: coherence.topic.v1.SubscriberId + (*SubscriberGroupId)(nil), // 27: coherence.topic.v1.SubscriberGroupId + (*InitializeSubscriptionRequest)(nil), // 28: coherence.topic.v1.InitializeSubscriptionRequest + (*InitializeSubscriptionResponse)(nil), // 29: coherence.topic.v1.InitializeSubscriptionResponse + (*EnsureSubscriptionRequest)(nil), // 30: coherence.topic.v1.EnsureSubscriptionRequest + (*TopicElement)(nil), // 31: coherence.topic.v1.TopicElement + (*ReceiveRequest)(nil), // 32: coherence.topic.v1.ReceiveRequest + (*ReceiveResponse)(nil), // 33: coherence.topic.v1.ReceiveResponse + (*SeekRequest)(nil), // 34: coherence.topic.v1.SeekRequest + (*SeekedPositions)(nil), // 35: coherence.topic.v1.SeekedPositions + (*SeekResponse)(nil), // 36: coherence.topic.v1.SeekResponse + (*CommitResponse)(nil), // 37: coherence.topic.v1.CommitResponse + (*ChannelAndPosition)(nil), // 38: coherence.topic.v1.ChannelAndPosition + (*MapOfChannelAndPosition)(nil), // 39: coherence.topic.v1.MapOfChannelAndPosition + (*MapOfChannelAndTimestamp)(nil), // 40: coherence.topic.v1.MapOfChannelAndTimestamp + nil, // 41: coherence.topic.v1.SeekResponse.PositionsEntry + nil, // 42: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry + nil, // 43: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry + (*anypb.Any)(nil), // 44: google.protobuf.Any + (*v1.ErrorMessage)(nil), // 45: coherence.common.v1.ErrorMessage + (*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp +} +var file_topic_service_messages_v1_proto_depIdxs = []int32{ + 0, // 0: coherence.topic.v1.TopicServiceRequest.type:type_name -> coherence.topic.v1.TopicServiceRequestType + 44, // 1: coherence.topic.v1.TopicServiceRequest.message:type_name -> google.protobuf.Any + 1, // 2: coherence.topic.v1.TopicServiceResponse.type:type_name -> coherence.topic.v1.ResponseType + 44, // 3: coherence.topic.v1.TopicServiceResponse.message:type_name -> google.protobuf.Any + 2, // 4: coherence.topic.v1.NamedTopicEvent.type:type_name -> coherence.topic.v1.TopicEventType + 3, // 5: coherence.topic.v1.PublisherEvent.type:type_name -> coherence.topic.v1.PublisherEventType + 22, // 6: coherence.topic.v1.PublishedValueStatus.position:type_name -> coherence.topic.v1.TopicPosition + 45, // 7: coherence.topic.v1.PublishedValueStatus.error:type_name -> coherence.common.v1.ErrorMessage + 4, // 8: coherence.topic.v1.PublishResult.status:type_name -> coherence.topic.v1.PublishStatus + 19, // 9: coherence.topic.v1.PublishResult.valueStatus:type_name -> coherence.topic.v1.PublishedValueStatus + 44, // 10: coherence.topic.v1.TopicPosition.position:type_name -> google.protobuf.Any + 26, // 11: coherence.topic.v1.EnsureSubscriberResponse.subscriberId:type_name -> coherence.topic.v1.SubscriberId + 27, // 12: coherence.topic.v1.EnsureSubscriberResponse.groupId:type_name -> coherence.topic.v1.SubscriberGroupId + 5, // 13: coherence.topic.v1.SubscriberEvent.type:type_name -> coherence.topic.v1.SubscriberEventType + 46, // 14: coherence.topic.v1.InitializeSubscriptionResponse.timestamp:type_name -> google.protobuf.Timestamp + 22, // 15: coherence.topic.v1.InitializeSubscriptionResponse.heads:type_name -> coherence.topic.v1.TopicPosition + 22, // 16: coherence.topic.v1.TopicElement.position:type_name -> coherence.topic.v1.TopicPosition + 46, // 17: coherence.topic.v1.TopicElement.timestamp:type_name -> google.protobuf.Timestamp + 6, // 18: coherence.topic.v1.ReceiveResponse.status:type_name -> coherence.topic.v1.ReceiveStatus + 22, // 19: coherence.topic.v1.ReceiveResponse.headPosition:type_name -> coherence.topic.v1.TopicPosition + 39, // 20: coherence.topic.v1.SeekRequest.byPosition:type_name -> coherence.topic.v1.MapOfChannelAndPosition + 40, // 21: coherence.topic.v1.SeekRequest.byTimestamp:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp + 22, // 22: coherence.topic.v1.SeekedPositions.head:type_name -> coherence.topic.v1.TopicPosition + 22, // 23: coherence.topic.v1.SeekedPositions.seekedTo:type_name -> coherence.topic.v1.TopicPosition + 41, // 24: coherence.topic.v1.SeekResponse.positions:type_name -> coherence.topic.v1.SeekResponse.PositionsEntry + 22, // 25: coherence.topic.v1.CommitResponse.position:type_name -> coherence.topic.v1.TopicPosition + 22, // 26: coherence.topic.v1.CommitResponse.head:type_name -> coherence.topic.v1.TopicPosition + 7, // 27: coherence.topic.v1.CommitResponse.status:type_name -> coherence.topic.v1.CommitResponseStatus + 45, // 28: coherence.topic.v1.CommitResponse.error:type_name -> coherence.common.v1.ErrorMessage + 22, // 29: coherence.topic.v1.ChannelAndPosition.position:type_name -> coherence.topic.v1.TopicPosition + 42, // 30: coherence.topic.v1.MapOfChannelAndPosition.positions:type_name -> coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry + 43, // 31: coherence.topic.v1.MapOfChannelAndTimestamp.timestamps:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry + 35, // 32: coherence.topic.v1.SeekResponse.PositionsEntry.value:type_name -> coherence.topic.v1.SeekedPositions + 22, // 33: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry.value:type_name -> coherence.topic.v1.TopicPosition + 46, // 34: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry.value:type_name -> google.protobuf.Timestamp + 35, // [35:35] is the sub-list for method output_type + 35, // [35:35] is the sub-list for method input_type + 35, // [35:35] is the sub-list for extension type_name + 35, // [35:35] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name +} + +func init() { file_topic_service_messages_v1_proto_init() } +func file_topic_service_messages_v1_proto_init() { + if File_topic_service_messages_v1_proto != nil { + return + } + file_topic_service_messages_v1_proto_msgTypes[0].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[1].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[4].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[5].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[6].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[10].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[11].OneofWrappers = []any{ + (*PublishedValueStatus_Position)(nil), + (*PublishedValueStatus_Error)(nil), + } + file_topic_service_messages_v1_proto_msgTypes[16].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[24].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[26].OneofWrappers = []any{ + (*SeekRequest_ByPosition)(nil), + (*SeekRequest_ByTimestamp)(nil), + } + file_topic_service_messages_v1_proto_msgTypes[29].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_topic_service_messages_v1_proto_rawDesc), len(file_topic_service_messages_v1_proto_rawDesc)), + NumEnums: 8, + NumMessages: 36, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_topic_service_messages_v1_proto_goTypes, + DependencyIndexes: file_topic_service_messages_v1_proto_depIdxs, + EnumInfos: file_topic_service_messages_v1_proto_enumTypes, + MessageInfos: file_topic_service_messages_v1_proto_msgTypes, + }.Build() + File_topic_service_messages_v1_proto = out.File + file_topic_service_messages_v1_proto_goTypes = nil + file_topic_service_messages_v1_proto_depIdxs = nil +} diff --git a/proto/v1/proxy_service_messages_v1.pb.go b/proto/v1/proxy_service_messages_v1.pb.go index 00a2133..71696cc 100644 --- a/proto/v1/proxy_service_messages_v1.pb.go +++ b/proto/v1/proxy_service_messages_v1.pb.go @@ -327,7 +327,9 @@ type InitRequest struct { // The requested frequency that heartbeat messages should be sent by the server (in millis) Heartbeat *int64 `protobuf:"varint,7,opt,name=heartbeat,proto3,oneof" json:"heartbeat,omitempty"` // The optional client UUID (usually from Coherence clients that have a local Member UUID). - ClientUuid []byte `protobuf:"bytes,8,opt,name=clientUuid,proto3,oneof" json:"clientUuid,omitempty"` + ClientUuid []byte `protobuf:"bytes,8,opt,name=clientUuid,proto3,oneof" json:"clientUuid,omitempty"` + // The client's member identity + Identity *ClientMemberIdentity `protobuf:"bytes,9,opt,name=identity,proto3,oneof" json:"identity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -411,6 +413,13 @@ func (x *InitRequest) GetClientUuid() []byte { return nil } +func (x *InitRequest) GetIdentity() *ClientMemberIdentity { + if x != nil { + return x.Identity + } + return nil +} + // The response to an InitRequest type InitResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -502,6 +511,133 @@ func (x *InitResponse) GetProxyMemberUuid() []byte { return nil } +type ClientMemberIdentity struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the cluster with which this member is associated. + ClusterName *string `protobuf:"bytes,1,opt,name=clusterName,proto3,oneof" json:"clusterName,omitempty"` + // The Member's machine Id. This identifier should be the same for Members that are on + // the same physical machine, and ideally different for Members that are on different + // physical machines. + MachineId int32 `protobuf:"varint,2,opt,name=machineId,proto3" json:"machineId,omitempty"` + // The configured name for the Machine (such as a host name) in which this Member resides. + // This name is used for logging purposes and to differentiate among multiple servers, + // and may be used as the basis for determining the MachineId property. + MachineName *string `protobuf:"bytes,3,opt,name=machineName,proto3,oneof" json:"machineName,omitempty"` + // The configured name for the Member. This name is used for logging purposes and + // to differentiate among Members running within a particular process. + MemberName *string `protobuf:"bytes,4,opt,name=memberName,proto3,oneof" json:"memberName,omitempty"` + // The member priority + Priority int32 `protobuf:"varint,5,opt,name=priority,proto3" json:"priority,omitempty"` + // The configured name for the Process (such as a JVM) in which this Member resides. + // This name is used for logging purposes and to differentiate among multiple processes on a a single machine. + ProcessName *string `protobuf:"bytes,6,opt,name=processName,proto3,oneof" json:"processName,omitempty"` + // The configured name for the Rack (such as a physical rack, cage or blade frame) in which + // this Member resides. This name is used for logging purposes and to differentiate among multiple + // racks within a particular data center, for example. + RackName *string `protobuf:"bytes,7,opt,name=rackName,proto3,oneof" json:"rackName,omitempty"` + // The configured name for the Site (such as a data center) in which this Member resides. + // This name is used for logging purposes and to differentiate among multiple geographic sites. + SiteName *string `protobuf:"bytes,8,opt,name=siteName,proto3,oneof" json:"siteName,omitempty"` + // The configured role name for the Member. This role is completely definable by the application, + // and can be used to determine what Members to use for specific purposes. + RoleName *string `protobuf:"bytes,9,opt,name=roleName,proto3,oneof" json:"roleName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientMemberIdentity) Reset() { + *x = ClientMemberIdentity{} + mi := &file_proxy_service_messages_v1_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientMemberIdentity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientMemberIdentity) ProtoMessage() {} + +func (x *ClientMemberIdentity) ProtoReflect() protoreflect.Message { + mi := &file_proxy_service_messages_v1_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientMemberIdentity.ProtoReflect.Descriptor instead. +func (*ClientMemberIdentity) Descriptor() ([]byte, []int) { + return file_proxy_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +func (x *ClientMemberIdentity) GetClusterName() string { + if x != nil && x.ClusterName != nil { + return *x.ClusterName + } + return "" +} + +func (x *ClientMemberIdentity) GetMachineId() int32 { + if x != nil { + return x.MachineId + } + return 0 +} + +func (x *ClientMemberIdentity) GetMachineName() string { + if x != nil && x.MachineName != nil { + return *x.MachineName + } + return "" +} + +func (x *ClientMemberIdentity) GetMemberName() string { + if x != nil && x.MemberName != nil { + return *x.MemberName + } + return "" +} + +func (x *ClientMemberIdentity) GetPriority() int32 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *ClientMemberIdentity) GetProcessName() string { + if x != nil && x.ProcessName != nil { + return *x.ProcessName + } + return "" +} + +func (x *ClientMemberIdentity) GetRackName() string { + if x != nil && x.RackName != nil { + return *x.RackName + } + return "" +} + +func (x *ClientMemberIdentity) GetSiteName() string { + if x != nil && x.SiteName != nil { + return *x.SiteName + } + return "" +} + +func (x *ClientMemberIdentity) GetRoleName() string { + if x != nil && x.RoleName != nil { + return *x.RoleName + } + return "" +} + var File_proxy_service_messages_v1_proto protoreflect.FileDescriptor const file_proxy_service_messages_v1_proto_rawDesc = "" + @@ -529,7 +665,7 @@ const file_proxy_service_messages_v1_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12*\n" + "\x05value\x18\x02 \x01(\v2\x14.google.protobuf.AnyR\x05value:\x028\x01B\n" + "\n" + - "\bresponse\"\xa2\x02\n" + + "\bresponse\"\xfa\x02\n" + "\vInitRequest\x12\x14\n" + "\x05scope\x18\x02 \x01(\tR\x05scope\x12\x16\n" + "\x06format\x18\x03 \x01(\tR\x06format\x12\x1a\n" + @@ -539,17 +675,38 @@ const file_proxy_service_messages_v1_proto_rawDesc = "" + "\theartbeat\x18\a \x01(\x03H\x00R\theartbeat\x88\x01\x01\x12#\n" + "\n" + "clientUuid\x18\b \x01(\fH\x01R\n" + - "clientUuid\x88\x01\x01B\f\n" + + "clientUuid\x88\x01\x01\x12I\n" + + "\bidentity\x18\t \x01(\v2(.coherence.proxy.v1.ClientMemberIdentityH\x02R\bidentity\x88\x01\x01B\f\n" + "\n" + "_heartbeatB\r\n" + - "\v_clientUuid\"\xde\x01\n" + + "\v_clientUuidB\v\n" + + "\t_identity\"\xde\x01\n" + "\fInitResponse\x12\x12\n" + "\x04uuid\x18\x01 \x01(\fR\x04uuid\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12&\n" + "\x0eencodedVersion\x18\x03 \x01(\x05R\x0eencodedVersion\x12(\n" + "\x0fprotocolVersion\x18\x04 \x01(\x05R\x0fprotocolVersion\x12$\n" + "\rproxyMemberId\x18\x05 \x01(\x05R\rproxyMemberId\x12(\n" + - "\x0fproxyMemberUuid\x18\x06 \x01(\fR\x0fproxyMemberUuidB_\n" + + "\x0fproxyMemberUuid\x18\x06 \x01(\fR\x0fproxyMemberUuid\"\xb3\x03\n" + + "\x14ClientMemberIdentity\x12%\n" + + "\vclusterName\x18\x01 \x01(\tH\x00R\vclusterName\x88\x01\x01\x12\x1c\n" + + "\tmachineId\x18\x02 \x01(\x05R\tmachineId\x12%\n" + + "\vmachineName\x18\x03 \x01(\tH\x01R\vmachineName\x88\x01\x01\x12#\n" + + "\n" + + "memberName\x18\x04 \x01(\tH\x02R\n" + + "memberName\x88\x01\x01\x12\x1a\n" + + "\bpriority\x18\x05 \x01(\x05R\bpriority\x12%\n" + + "\vprocessName\x18\x06 \x01(\tH\x03R\vprocessName\x88\x01\x01\x12\x1f\n" + + "\brackName\x18\a \x01(\tH\x04R\brackName\x88\x01\x01\x12\x1f\n" + + "\bsiteName\x18\b \x01(\tH\x05R\bsiteName\x88\x01\x01\x12\x1f\n" + + "\broleName\x18\t \x01(\tH\x06R\broleName\x88\x01\x01B\x0e\n" + + "\f_clusterNameB\x0e\n" + + "\f_machineNameB\r\n" + + "\v_memberNameB\x0e\n" + + "\f_processNameB\v\n" + + "\t_rackNameB\v\n" + + "\t_siteNameB\v\n" + + "\t_roleNameB_\n" + "+com.oracle.coherence.grpc.messages.proxy.v1P\x01Z.github.com/oracle/coherence-go-client/proto/v1b\x06proto3" var ( @@ -564,37 +721,39 @@ func file_proxy_service_messages_v1_proto_rawDescGZIP() []byte { return file_proxy_service_messages_v1_proto_rawDescData } -var file_proxy_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_proxy_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_proxy_service_messages_v1_proto_goTypes = []any{ - (*ProxyRequest)(nil), // 0: coherence.proxy.v1.ProxyRequest - (*ProxyResponse)(nil), // 1: coherence.proxy.v1.ProxyResponse - (*InitRequest)(nil), // 2: coherence.proxy.v1.InitRequest - (*InitResponse)(nil), // 3: coherence.proxy.v1.InitResponse - nil, // 4: coherence.proxy.v1.ProxyRequest.ContextEntry - nil, // 5: coherence.proxy.v1.ProxyResponse.ContextEntry - (*anypb.Any)(nil), // 6: google.protobuf.Any - (*HeartbeatMessage)(nil), // 7: coherence.common.v1.HeartbeatMessage - (*ErrorMessage)(nil), // 8: coherence.common.v1.ErrorMessage - (*Complete)(nil), // 9: coherence.common.v1.Complete + (*ProxyRequest)(nil), // 0: coherence.proxy.v1.ProxyRequest + (*ProxyResponse)(nil), // 1: coherence.proxy.v1.ProxyResponse + (*InitRequest)(nil), // 2: coherence.proxy.v1.InitRequest + (*InitResponse)(nil), // 3: coherence.proxy.v1.InitResponse + (*ClientMemberIdentity)(nil), // 4: coherence.proxy.v1.ClientMemberIdentity + nil, // 5: coherence.proxy.v1.ProxyRequest.ContextEntry + nil, // 6: coherence.proxy.v1.ProxyResponse.ContextEntry + (*anypb.Any)(nil), // 7: google.protobuf.Any + (*HeartbeatMessage)(nil), // 8: coherence.common.v1.HeartbeatMessage + (*ErrorMessage)(nil), // 9: coherence.common.v1.ErrorMessage + (*Complete)(nil), // 10: coherence.common.v1.Complete } var file_proxy_service_messages_v1_proto_depIdxs = []int32{ 2, // 0: coherence.proxy.v1.ProxyRequest.init:type_name -> coherence.proxy.v1.InitRequest - 6, // 1: coherence.proxy.v1.ProxyRequest.message:type_name -> google.protobuf.Any - 7, // 2: coherence.proxy.v1.ProxyRequest.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage - 4, // 3: coherence.proxy.v1.ProxyRequest.context:type_name -> coherence.proxy.v1.ProxyRequest.ContextEntry + 7, // 1: coherence.proxy.v1.ProxyRequest.message:type_name -> google.protobuf.Any + 8, // 2: coherence.proxy.v1.ProxyRequest.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage + 5, // 3: coherence.proxy.v1.ProxyRequest.context:type_name -> coherence.proxy.v1.ProxyRequest.ContextEntry 3, // 4: coherence.proxy.v1.ProxyResponse.init:type_name -> coherence.proxy.v1.InitResponse - 6, // 5: coherence.proxy.v1.ProxyResponse.message:type_name -> google.protobuf.Any - 8, // 6: coherence.proxy.v1.ProxyResponse.error:type_name -> coherence.common.v1.ErrorMessage - 9, // 7: coherence.proxy.v1.ProxyResponse.complete:type_name -> coherence.common.v1.Complete - 7, // 8: coherence.proxy.v1.ProxyResponse.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage - 5, // 9: coherence.proxy.v1.ProxyResponse.context:type_name -> coherence.proxy.v1.ProxyResponse.ContextEntry - 6, // 10: coherence.proxy.v1.ProxyRequest.ContextEntry.value:type_name -> google.protobuf.Any - 6, // 11: coherence.proxy.v1.ProxyResponse.ContextEntry.value:type_name -> google.protobuf.Any - 12, // [12:12] is the sub-list for method output_type - 12, // [12:12] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 7, // 5: coherence.proxy.v1.ProxyResponse.message:type_name -> google.protobuf.Any + 9, // 6: coherence.proxy.v1.ProxyResponse.error:type_name -> coherence.common.v1.ErrorMessage + 10, // 7: coherence.proxy.v1.ProxyResponse.complete:type_name -> coherence.common.v1.Complete + 8, // 8: coherence.proxy.v1.ProxyResponse.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage + 6, // 9: coherence.proxy.v1.ProxyResponse.context:type_name -> coherence.proxy.v1.ProxyResponse.ContextEntry + 4, // 10: coherence.proxy.v1.InitRequest.identity:type_name -> coherence.proxy.v1.ClientMemberIdentity + 7, // 11: coherence.proxy.v1.ProxyRequest.ContextEntry.value:type_name -> google.protobuf.Any + 7, // 12: coherence.proxy.v1.ProxyResponse.ContextEntry.value:type_name -> google.protobuf.Any + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_proxy_service_messages_v1_proto_init() } @@ -616,13 +775,14 @@ func file_proxy_service_messages_v1_proto_init() { (*ProxyResponse_Heartbeat)(nil), } file_proxy_service_messages_v1_proto_msgTypes[2].OneofWrappers = []any{} + file_proxy_service_messages_v1_proto_msgTypes[4].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_service_messages_v1_proto_rawDesc), len(file_proxy_service_messages_v1_proto_rawDesc)), NumEnums: 0, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/scripts/run-compat-ce.sh b/scripts/run-compat-ce.sh index 51cf131..1485d63 100755 --- a/scripts/run-compat-ce.sh +++ b/scripts/run-compat-ce.sh @@ -61,3 +61,6 @@ COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax echo "Coherence CE 25.03.1 All Tests gRPC v1" COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=25.03.1 make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone + +echo "Coherence CE 25.09-SNAPSHOT with topics" +COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax,queues COHERENCE_VERSION=25.09-SNAPSHOT make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone-topics diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh new file mode 100755 index 0000000..927809c --- /dev/null +++ b/scripts/run-test-dapr.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# + +# $3 = true to enable TLS test + +set -e + +if [ $# -lt 2 ]; then + echo "You must specify the dapr test directory and dapr dir to install to" + exit 1 +fi + +TLS=false + +if [ $# -eq 3 -a "$3" == "true" ]; then + TLS=true +fi + +DIR=`pwd` +DAPR_TEST_DIR=$1 +DAPR_TEST_HOME=$2 + +mkdir -p $DAPR_TEST_HOME + +echo "DAPR Test Dir: $DAPR_TEST_DIR" +echo "DAPR Test Home: $DAPR_TEST_HOME" +echo "TLS: $TLS" + +echo "Install DAPR" +OS=`uname` + +echo "Listing docker images" +docker ps + +if [ "$OS" == "Linux" ]; then + wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash + curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash + cohctl version + # Wait for coherence + cohctl monitor health -e http://127.0.0.1:6676/,http://127.0.0.1:6677/ -T 120 -w -I + cohctl add cluster default -u http://127.0.0.1:30000/management/coherence/cluster + cohctl version +else + echo "Assuming installed" + type dapr +fi + +dapr init +dapr version +ls -l ~/.dapr + +echo +echo "Cloning repositories..." +cd $DAPR_TEST_HOME +rm -rf dapr || true +git clone https://github.com/dapr/dapr.git +cd dapr + +# Test with the current go client +go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../.. + +echo "Building dapr core..." +make modtidy-all +make DEBUG=1 build + +export DAPR_DIST=$(echo dist/*/debug) +DAPR_HOME=$DAPR_TEST_HOME/dapr/$DAPR_DIST +DAPR_BIN=$DAPR_HOME/daprd + +ls -l $DAPR_BIN + +echo "Install $DAPR_BIN to ~/.dapr/bin" +cp $DAPR_BIN ~/.dapr/bin + +echo "Running Test" + +cd $DAPR_TEST_DIR/my-dapr-app + +go mod tidy + +COMPONENTS=./components/ +if [ "$TLS" == "true" ]; then + COMPONENTS=./components-tls/ + echo "DIR = $DIR" + pwd + ls -l ../../utils/certs/guardians-ca.crt +fi + +echo "Running DAPR with component $COMPONENTS" + +dapr run --app-id myapp --resources-path $COMPONENTS --log-level debug -- go run main.go + +# Verify the caches +cohctl get caches -o wide + + + + diff --git a/test/dapr/my-dapr-app/components-tls/coherence.yaml b/test/dapr/my-dapr-app/components-tls/coherence.yaml new file mode 100644 index 0000000..6cbe82c --- /dev/null +++ b/test/dapr/my-dapr-app/components-tls/coherence.yaml @@ -0,0 +1,25 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.coherence + version: v1 + metadata: + - name: serverAddress + value: localhost:1408 + - name: tlsEnabled + value: true + - name: tlsClientCertPath + value: ../../utils/certs/star-lord.crt + - name: tlsClientKey + value: ../../utils/certs/star-lord.key + - name: tlsCertsPath + value: ../../utils/certs/guardians-ca.crt + - name: ignoreInvalidCerts + value: true diff --git a/test/dapr/my-dapr-app/components/coherence.yaml b/test/dapr/my-dapr-app/components/coherence.yaml new file mode 100644 index 0000000..0eec1fa --- /dev/null +++ b/test/dapr/my-dapr-app/components/coherence.yaml @@ -0,0 +1,17 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.coherence + version: v1 + metadata: + - name: serverAddress + value: localhost:1408 + - name: tlsEnabled + value: false diff --git a/test/dapr/my-dapr-app/go.mod b/test/dapr/my-dapr-app/go.mod new file mode 100644 index 0000000..07cf19d --- /dev/null +++ b/test/dapr/my-dapr-app/go.mod @@ -0,0 +1,23 @@ +// +// Copyright (c) 2022, 2025 Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at +// https://oss.oracle.com/licenses/upl. +// +module main + +go 1.24.4 + +require github.com/dapr/go-sdk v1.12.0 + +require ( + github.com/dapr/dapr v1.15.6 // indirect + github.com/google/uuid v1.6.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/test/dapr/my-dapr-app/go.sum b/test/dapr/my-dapr-app/go.sum new file mode 100644 index 0000000..9756e33 --- /dev/null +++ b/test/dapr/my-dapr-app/go.sum @@ -0,0 +1,48 @@ +github.com/dapr/dapr v1.15.6 h1:kBr/THNZAHUK9h2pfItCgFcjS81Rp2XPldW9BpCn/vg= +github.com/dapr/dapr v1.15.6/go.mod h1:P1liLDr2uNyLweww2vCwazqJlnEtvbu7zWHTjOUmb6k= +github.com/dapr/go-sdk v1.12.0 h1:+9IHZ1faWwNg/HvZk1ht0oIU8eqOa9nvGMk+Nr+0qkc= +github.com/dapr/go-sdk v1.12.0/go.mod h1:RpZJ/pNfODlyk6x+whdtCrFI1/o0X67LCSwZeAZa64U= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/dapr/my-dapr-app/main.go b/test/dapr/my-dapr-app/main.go new file mode 100644 index 0000000..a267089 --- /dev/null +++ b/test/dapr/my-dapr-app/main.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 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 main + +import ( + "context" + "fmt" + "log" + + daprd "github.com/dapr/go-sdk/client" +) + +func main() { + client, err := daprd.NewClient() + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + defer client.Close() + + ctx := context.Background() + emptyMetadata := map[string]string{} + + key := "foo" + val := []byte("bar") + + if err = client.SaveState(ctx, "statestore", key, val, emptyMetadata); err != nil { + log.Fatalf("save error: %v", err) + } + + item, err := client.GetState(ctx, "statestore", key, emptyMetadata) + if err != nil { + log.Fatalf("get error: %v", err) + } + + fmt.Printf("Got state: %s = %s\n", key, item.Value) +} diff --git a/test/e2e/topics/doc.go b/test/e2e/topics/doc.go new file mode 100644 index 0000000..bfe7676 --- /dev/null +++ b/test/e2e/topics/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 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 topic provides integration tests for the coherence-go-client with topic for 25.09 and above. +*/ +package topics diff --git a/test/e2e/topics/publisher_event_test.go b/test/e2e/topics/publisher_event_test.go new file mode 100644 index 0000000..06602c9 --- /dev/null +++ b/test/e2e/topics/publisher_event_test.go @@ -0,0 +1,104 @@ +/* + * Copyright (c) 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 topics + +import ( + "context" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/test/utils" + "sync/atomic" + "testing" + "time" +) + +func TestPublisherEventsDestroyOnServer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ) + + const topicName = "publisher-events" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + pub, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingPublisherListener[string](topicName) + + g.Expect(pub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + _, err = utils.IssueGetRequest(utils.GetTestContext().RestURL + "/destroyTopic/" + topicName) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + g.Eventually(func() int32 { return listener.destroyCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) + + g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) +} + +func TestPublisherEventsDestroyByClient(t *testing.T) { + var ( + g = gomega.NewWithT(t) + ctx = context.Background() + ) + + const topicName = "publisher-events-client-destroy" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + pub, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingPublisherListener[string](topicName) + + g.Expect(pub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(pub.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + utils.Sleep(1) + + g.Eventually(func() int32 { return listener.releaseCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) +} + +func NewCountingPublisherListener[V any](name string) *CountingPublisherListener[V] { + countingListener := CountingPublisherListener[V]{ + name: name, + listener: coherence.NewPublisherLifecycleListener[V](), + } + + countingListener.listener.OnDestroyed(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.destroyCount, 1) + }).OnReleased(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnAny(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.allCount, 1) + }) + + return &countingListener +} + +type CountingPublisherListener[V any] struct { + listener coherence.PublisherLifecycleListener[V] + name string + releaseCount int32 + destroyCount int32 + allCount int32 +} diff --git a/test/e2e/topics/subscriber_event_test.go b/test/e2e/topics/subscriber_event_test.go new file mode 100644 index 0000000..8c1a4ce --- /dev/null +++ b/test/e2e/topics/subscriber_event_test.go @@ -0,0 +1,104 @@ +/* + * Copyright (c) 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 topics + +import ( + "context" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/test/utils" + "sync/atomic" + "testing" + "time" +) + +func TestSubscriberEventsDestroyOnServer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ) + + const topicName = "subscriber-events" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + sub, err := topic1.CreateSubscriber(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingSubscriberListener[string](topicName) + + g.Expect(sub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + _, err = utils.IssueGetRequest(utils.GetTestContext().RestURL + "/destroyTopic/" + topicName) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + g.Eventually(func() int32 { return listener.destroyCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) + + g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) +} + +func TestSubscriberEventsDestroyByClient(t *testing.T) { + var ( + g = gomega.NewWithT(t) + ctx = context.Background() + ) + + const topicName = "subscriber-events-client-destroy" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + sub, err := topic1.CreateSubscriber(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingSubscriberListener[string](topicName) + + g.Expect(sub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(sub.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + utils.Sleep(1) + + g.Eventually(func() int32 { return listener.releaseCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) +} + +func NewCountingSubscriberListener[V any](name string) *CountingSubscriberListener[V] { + countingListener := CountingSubscriberListener[V]{ + name: name, + listener: coherence.NewSubscriberLifecycleListener[V](), + } + + countingListener.listener.OnDestroyed(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.destroyCount, 1) + }).OnReleased(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnAny(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.allCount, 1) + }) + + return &countingListener +} + +type CountingSubscriberListener[V any] struct { + listener coherence.SubscriberLifecycleListener[V] + name string + releaseCount int32 + destroyCount int32 + allCount int32 +} diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go new file mode 100644 index 0000000..876e7dc --- /dev/null +++ b/test/e2e/topics/subscriber_test.go @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024, 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 topics + +import ( + "context" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/coherence/extractors" + "github.com/oracle/coherence-go-client/v2/coherence/filters" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" + "github.com/oracle/coherence-go-client/v2/coherence/subscribergroup" + "github.com/oracle/coherence-go-client/v2/test/utils" + "log" + "testing" +) + +func TestSubscriberWithFilter(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon-filter" + + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) + defer session1.Close() + + // create a subscriber with a filter + sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + runTestPerson(g, topic1, sub1) +} + +func TestSubscriberWithTransformer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon-transformer" + + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) + defer session1.Close() + + extractor := extractors.Extract[string]("name") + // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, + subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + runTestPerson(g, topic1, sub1) +} + +func TestSubscriberWithTransformerAndFilter(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon-transformer" + + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) + defer session1.Close() + + extractor := extractors.Extract[string]("name") + // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + publishEntriesPerson(g, pub1, 1_000) + + utils.Sleep(5) + + err = sub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = sub1.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} + +func TestSubscriberGroupWithinTopic(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const ( + topicName = "my-topic-with-sg" + subGroup = "sub-group-1" + ) + + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) + defer session1.Close() + + err = topic1.CreateSubscriberGroup(ctx, subGroup) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + sub1, err := topic1.CreateSubscriber(ctx, subscriber.InSubscriberGroup(subGroup)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // destroy the subscriber group from the subscriber only + g.Expect(sub1.DestroySubscriberGroup(ctx, subGroup)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(sub1.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) +} + +func TestSubscriberGroupWithinSubscriber(t *testing.T) { + runTestSubscriberGroup(gomega.NewWithT(t), "sub-group-1") +} + +func TestSubscriberGroupWithinSubscriberAndFilter(t *testing.T) { + runTestSubscriberGroup(gomega.NewWithT(t), "sub-group-2", subscribergroup.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) +} + +func runTestSubscriberGroup(g *gomega.WithT, subscriberGroup string, options ...func(o *subscribergroup.Options)) { + var ( + err error + ctx = context.Background() + ) + + const ( + topicName = "my-topic-with-sg" + ) + + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) + defer session1.Close() + + err = topic1.CreateSubscriberGroup(ctx, subscriberGroup, options...) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + sub1, err := topic1.CreateSubscriber(ctx, subscriber.InSubscriberGroup(subscriberGroup)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // destroy the subscriber group from the subscriber only + g.Expect(sub1.DestroySubscriberGroup(ctx, subscriberGroup)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(sub1.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) +} diff --git a/test/e2e/topics/suite_test.go b/test/e2e/topics/suite_test.go new file mode 100644 index 0000000..e03ba8e --- /dev/null +++ b/test/e2e/topics/suite_test.go @@ -0,0 +1,17 @@ +/* + * Copyright (c) 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 topics + +import ( + "github.com/oracle/coherence-go-client/v2/test/utils" + "testing" +) + +// The entry point for the test suite +func TestMain(m *testing.M) { + utils.RunTest(m, 1408, 30000, 8080, false) +} diff --git a/test/e2e/topics/topics_event_test.go b/test/e2e/topics/topics_event_test.go new file mode 100644 index 0000000..e5f026d --- /dev/null +++ b/test/e2e/topics/topics_event_test.go @@ -0,0 +1,134 @@ +/* + * Copyright (c) 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 topics + +import ( + "context" + "fmt" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/test/utils" + "log" + "sync/atomic" + "testing" + "time" +) + +func TestTopicsEventsDestroyOnServer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ) + + const topicName = "topics-events" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + utils.Sleep(5) + + listener := NewCountingTopicListener[string](topicName) + + g.Expect(topic1.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + _, err = utils.IssueGetRequest(utils.GetTestContext().RestURL + "/destroyTopic/" + topicName) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + g.Eventually(func() int32 { return listener.destroyCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) + + g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) +} + +func TestTopicsEventsDestroyByClient(t *testing.T) { + var ( + g = gomega.NewWithT(t) + ctx = context.Background() + ) + + const topicName = "topics-events-client-destroy" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + utils.Sleep(5) + + listener := NewCountingTopicListener[string](topicName) + + g.Expect(topic1.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) + utils.Sleep(5) + + g.Eventually(func() int32 { return listener.destroyCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) + + g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) +} + +func TestTopicsEventsPublisherDestroy(t *testing.T) { + var ( + g = gomega.NewWithT(t) + ctx = context.Background() + ) + + const topicName = "topics-events-publisher-destroy" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + utils.Sleep(5) + + sub1, err := topic1.CreateSubscriber(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + fmt.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + // destroy the topic and we should see some messages for publisher and subscriber + _, err = utils.IssueGetRequest(utils.GetTestContext().RestURL + "/destroyTopic/" + topicName) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + fmt.Println("Done") +} + +func NewCountingTopicListener[V any](name string) *CountingTopicListener[V] { + countingListener := CountingTopicListener[V]{ + name: name, + listener: coherence.NewTopicLifecycleListener[V](), + } + + countingListener.listener.OnDestroyed(func(_ coherence.TopicLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.destroyCount, 1) + }).OnReleased(func(_ coherence.TopicLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnAny(func(_ coherence.TopicLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.allCount, 1) + }) + + return &countingListener +} + +type CountingTopicListener[V any] struct { + listener coherence.TopicLifecycleListener[V] + name string + truncateCount int32 + destroyCount int32 + releaseCount int32 + allCount int32 +} diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go new file mode 100644 index 0000000..5a4a739 --- /dev/null +++ b/test/e2e/topics/topics_test.go @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2024, 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 topics + +import ( + "context" + "fmt" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/coherence/publisher" + "github.com/oracle/coherence-go-client/v2/coherence/topic" + "github.com/oracle/coherence-go-client/v2/test/utils" + "log" + "testing" + "time" +) + +func TestBasicTopicCreatedAndDestroy(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + utils.Sleep(5) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // test again and we should get error + err = topic1.Destroy(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) +} + +func TestTopicPublish(t *testing.T) { + g := gomega.NewWithT(t) + + RunTestBasicTopicAnonPubSub(g) + RunTestBasicTopicAnonPubSub(g, publisher.WithDefaultOrdering()) + RunTestBasicTopicAnonPubSub(g, publisher.WithRoundRobinOrdering()) + RunTestBasicTopicAnonPubSub(g, publisher.WithChannelCount(21)) +} + +func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publisher.Options)) { + var ( + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon" + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + // create a subscriber first + sub1, err := topic1.CreateSubscriber(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + publishEntriesString(g, pub1, 1_000) + + utils.Sleep(5) + + err = sub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = pub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} + +func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + ctx = context.Background() + ) + + const topicName = "my-topic-anon-2" + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + sub1, err := coherence.CreateSubscriber[string](ctx, session1, topicName) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + fmt.Println("Subscriber created", sub1) + + pub1, err := coherence.CreatePublisher[string](ctx, session1, topicName) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + publishEntriesString(g, pub1, 1_000) + + utils.Sleep(5) + + err = pub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // try to close again + err = pub1.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) + + // get the topic so we can destroy + topic1, err := coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} + +func runTestPerson[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E]) { + ctx := context.Background() + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + for i := 1; i <= 1_000; i++ { + p := utils.Person{ + ID: i, + Name: fmt.Sprintf("my-value-%d", i), + Age: 10 + i, + } + status, err2 := pub1.Publish(ctx, p) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } + + utils.Sleep(5) + + err = s.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = s.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) +} + +func runTestString[E any](g *gomega.WithT, topic1 coherence.NamedTopic[string], s coherence.Subscriber[E]) { + ctx := context.Background() + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + for i := 1; i <= 1_000; i++ { + status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } + + utils.Sleep(5) + + err = s.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = s.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) +} + +func publishEntriesPerson(g *gomega.WithT, pub coherence.Publisher[utils.Person], count int) { + ctx := context.Background() + for i := 1; i <= count; i++ { + p := utils.Person{ + ID: i, + Name: fmt.Sprintf("my-value-%d", i), + Age: 10 + i, + } + status, err2 := pub.Publish(ctx, p) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } +} + +func publishEntriesString(g *gomega.WithT, pub coherence.Publisher[string], count int) { + ctx := context.Background() + for i := 1; i <= count; i++ { + status, err2 := pub.Publish(ctx, fmt.Sprintf("value-%d", i)) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } +} + +func getSessionAndTopic[V any](g *gomega.WithT, topicName string) (*coherence.Session, coherence.NamedTopic[V]) { + session1, err := utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + topic1, err := coherence.GetNamedTopic[V](context.Background(), session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + return session1, topic1 +} diff --git a/test/utils/docker-compose-2-members.yaml b/test/utils/docker-compose-2-members.yaml index 0b710fc..37a5a8e 100644 --- a/test/utils/docker-compose-2-members.yaml +++ b/test/utils/docker-compose-2-members.yaml @@ -1,4 +1,4 @@ -# Copyright 2023, 2024 Oracle Corporation and/or its affiliates. +# Copyright 2023, 2025 Oracle Corporation 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 @@ services: - 1408:1408 - 9612:9612 - 8080:8080 + - 6676:6676 volumes: - ./certs:/certs @@ -31,6 +32,7 @@ services: - COHERENCE_WKA=server1 ports: - 9613:9613 + - 6677:6677 networks: coherence: