This is an automated email from the ASF dual-hosted git repository.

chenguangsheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-eventmesh.git


The following commit(s) were added to refs/heads/master by this push:
     new 65e90dbff [ISSUE #3380] Add eventmesh-storage-pulsar module
     new ed162b70d Merge pull request #3383 from xwm1992/add-pulsar-storage
65e90dbff is described below

commit 65e90dbff44529fa792ae988d610770916c64c7f
Author: xwm1992 <[email protected]>
AuthorDate: Thu Mar 9 22:06:33 2023 +0800

    [ISSUE #3380] Add eventmesh-storage-pulsar module
---
 .../eventmesh-storage-pulsar/build.gradle          |  45 +++++
 .../eventmesh-storage-pulsar/gradle.properties     |  18 ++
 .../storage/pulsar/client/PulsarClientWrapper.java | 120 ++++++++++++
 .../storage/pulsar/config/ClientConfiguration.java |  46 +++++
 .../storage/pulsar/constant/PulsarConstant.java    |  23 +++
 .../pulsar/consumer/PulsarConsumerImpl.java        | 208 +++++++++++++++++++++
 .../storage/pulsar/producer/AbstractProducer.java  |  55 ++++++
 .../storage/pulsar/producer/ProducerImpl.java      |  83 ++++++++
 .../pulsar/producer/PulsarProducerImpl.java        |  99 ++++++++++
 .../storage/StorageResourceServicePulsarImpl.java  |  33 ++++
 .../storage/pulsar/utils/CloudEventUtils.java      |  32 ++++
 .../org.apache.eventmesh.api.consumer.Consumer     |  16 ++
 .../org.apache.eventmesh.api.producer.Producer     |  16 ++
 ...he.eventmesh.api.storage.StorageResourceService |  16 ++
 .../src/main/resources/pulsar-client.properties    |  18 ++
 .../pulsar/config/ClientConfigurationTest.java     |  50 +++++
 .../src/test/resources/pulsar-client.properties    |  20 ++
 settings.gradle                                    |   1 +
 18 files changed, 899 insertions(+)

diff --git a/eventmesh-storage/eventmesh-storage-pulsar/build.gradle 
b/eventmesh-storage/eventmesh-storage-pulsar/build.gradle
new file mode 100644
index 000000000..eb4da5c65
--- /dev/null
+++ b/eventmesh-storage/eventmesh-storage-pulsar/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+configurations {
+    implementation.exclude group: 'ch.qos.logback', module: 'logback-classic'
+    implementation.exclude group: 'log4j', module: 'log4j'
+}
+
+dependencies {
+    implementation project(":eventmesh-common")
+    implementation project(":eventmesh-storage:eventmesh-storage-api")
+    implementation 'org.apache.pulsar:pulsar-client:2.10.1'
+
+    testImplementation project(":eventmesh-storage:eventmesh-storage-api")
+    testImplementation project(":eventmesh-common")
+    testImplementation 'org.apache.pulsar:pulsar-client:2.10.1'
+
+    implementation 'io.cloudevents:cloudevents-json-jackson'
+
+    testImplementation 'io.cloudevents:cloudevents-json-jackson'
+
+    testImplementation "org.mockito:mockito-core"
+    testImplementation "org.powermock:powermock-module-junit4"
+    testImplementation "org.powermock:powermock-api-mockito2"
+
+    compileOnly 'org.projectlombok:lombok'
+    annotationProcessor 'org.projectlombok:lombok'
+
+    testCompileOnly 'org.projectlombok:lombok'
+    testAnnotationProcessor 'org.projectlombok:lombok'
+}
diff --git a/eventmesh-storage/eventmesh-storage-pulsar/gradle.properties 
b/eventmesh-storage/eventmesh-storage-pulsar/gradle.properties
new file mode 100644
index 000000000..da19b5bdd
--- /dev/null
+++ b/eventmesh-storage/eventmesh-storage-pulsar/gradle.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+pluginType=storage
+pluginName=pulsar
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/client/PulsarClientWrapper.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/client/PulsarClientWrapper.java
new file mode 100644
index 000000000..9ad3f95ae
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/client/PulsarClientWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.client;
+
+import org.apache.eventmesh.api.SendCallback;
+import org.apache.eventmesh.api.exception.StorageConnectorRuntimeException;
+import org.apache.eventmesh.common.Constants;
+import org.apache.eventmesh.storage.pulsar.config.ClientConfiguration;
+import org.apache.eventmesh.storage.pulsar.utils.CloudEventUtils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.pulsar.client.api.AuthenticationFactory;
+import org.apache.pulsar.client.api.ClientBuilder;
+import org.apache.pulsar.client.api.Producer;
+import org.apache.pulsar.client.api.PulsarClient;
+import org.apache.pulsar.client.api.PulsarClientException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.provider.EventFormatProvider;
+import io.cloudevents.jackson.JsonFormat;
+
+import com.google.common.base.Preconditions;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class PulsarClientWrapper {
+
+    private ClientConfiguration config;
+    private PulsarClient pulsarClient;
+    private Map<String, Producer<byte[]>> producerMap = new HashMap<>();
+
+    public PulsarClientWrapper(ClientConfiguration config, Properties 
properties)  {
+        this.config = config;
+        String token = properties.getProperty(Constants.PRODUCER_TOKEN);
+        try {
+            ClientBuilder clientBuilder = PulsarClient.builder()
+                .serviceUrl(config.getServiceAddr());
+
+            if (config.getAuthPlugin() != null) {
+                Preconditions.checkNotNull(config.getAuthParams(),
+                    "Authentication Enabled in pulsar cluster, Please set 
authParams in pulsar-client.properties");
+                clientBuilder.authentication(
+                    config.getAuthPlugin(),
+                    config.getAuthParams()
+                );
+            }
+            if (StringUtils.isNotBlank(token)) {
+                clientBuilder.authentication(
+                    AuthenticationFactory.token(token)
+                );
+            }
+
+            this.pulsarClient = clientBuilder.build();
+        } catch (PulsarClientException ex) {
+            throw new StorageConnectorRuntimeException(
+              String.format("Failed to connect pulsar cluster %s with 
exception: %s", config.getServiceAddr(), ex.getMessage()));
+        }
+    }
+
+    private Producer<byte[]> createProducer(String topic) {
+        try {
+            return this.pulsarClient.newProducer()
+                .topic(topic)
+                .batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS)
+                .sendTimeout(10, TimeUnit.SECONDS)
+                .blockIfQueueFull(true)
+                .create();
+        } catch (PulsarClientException ex) {
+            throw new StorageConnectorRuntimeException(
+              String.format("Failed to create pulsar producer for %s with 
exception: %s", topic, ex.getMessage()));
+        }
+    }
+
+    public void publish(CloudEvent cloudEvent, SendCallback sendCallback) {
+        String topic = config.getTopicPrefix() + cloudEvent.getSubject();
+        Producer<byte[]> producer = producerMap.computeIfAbsent(topic, k -> 
createProducer(topic));
+        try {
+            byte[] serializedCloudEvent = EventFormatProvider
+                .getInstance()
+                .resolveFormat(JsonFormat.CONTENT_TYPE)
+                .serialize(cloudEvent);
+            producer.sendAsync(serializedCloudEvent).thenAccept(messageId -> {
+                
sendCallback.onSuccess(CloudEventUtils.convertSendResult(cloudEvent));
+            });
+        } catch (Exception ex) {
+            log.error("Failed to publish cloudEvent for {} with exception: {}",
+                cloudEvent.getSubject(), ex.getMessage());
+        }
+    }
+
+    public void shutdown() throws PulsarClientException {
+        pulsarClient.close();
+        for (Map.Entry<String, Producer<byte[]>> producerEntry : 
producerMap.entrySet()) {
+            producerEntry.getValue().close();
+        }
+        producerMap.clear();
+    }
+
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/config/ClientConfiguration.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/config/ClientConfiguration.java
new file mode 100644
index 000000000..5dc94accc
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/config/ClientConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.config;
+
+import org.apache.eventmesh.common.config.Config;
+import org.apache.eventmesh.common.config.ConfigFiled;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Config(prefix = "eventMesh.server.pulsar", path = 
"classPath://pulsar-client.properties")
+public class ClientConfiguration {
+
+    @ConfigFiled(field = "service")
+    private String serviceAddr;
+
+    @ConfigFiled(field = "authPlugin")
+    private String authPlugin;
+
+    @ConfigFiled(field = "authParams")
+    private String authParams;
+
+    /*
+    *   the full format of topic needs a prefix, but the prefix cannot be 
passed in the url when the topic is carried
+    *
+    * */
+    @ConfigFiled(field = "topicPrefix")
+    private String topicPrefix;
+}
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/constant/PulsarConstant.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/constant/PulsarConstant.java
new file mode 100644
index 000000000..9c14f2aac
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/constant/PulsarConstant.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.constant;
+
+public class PulsarConstant {
+
+    public static final String KEY_SEPARATOR = "/";
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/consumer/PulsarConsumerImpl.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/consumer/PulsarConsumerImpl.java
new file mode 100644
index 000000000..bafd63745
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/consumer/PulsarConsumerImpl.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.consumer;
+
+import org.apache.eventmesh.api.AbstractContext;
+import org.apache.eventmesh.api.EventListener;
+import org.apache.eventmesh.api.EventMeshAction;
+import org.apache.eventmesh.api.EventMeshAsyncConsumeContext;
+import org.apache.eventmesh.api.consumer.Consumer;
+import org.apache.eventmesh.api.exception.StorageConnectorRuntimeException;
+import org.apache.eventmesh.common.Constants;
+import org.apache.eventmesh.common.config.Config;
+import org.apache.eventmesh.storage.pulsar.config.ClientConfiguration;
+import org.apache.eventmesh.storage.pulsar.constant.PulsarConstant;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.pulsar.client.api.AuthenticationFactory;
+import org.apache.pulsar.client.api.ClientBuilder;
+import org.apache.pulsar.client.api.MessageListener;
+import org.apache.pulsar.client.api.PulsarClient;
+import org.apache.pulsar.client.api.PulsarClientException;
+import org.apache.pulsar.client.api.SubscriptionMode;
+import org.apache.pulsar.client.api.SubscriptionType;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.format.EventDeserializationException;
+import io.cloudevents.core.provider.EventFormatProvider;
+import io.cloudevents.jackson.JsonFormat;
+
+import com.google.common.base.Preconditions;
+
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+@Config(field = "clientConfiguration")
+public class PulsarConsumerImpl implements Consumer {
+
+    private final AtomicBoolean started = new AtomicBoolean(false);
+    private Properties properties;
+    private PulsarClient pulsarClient;
+    private EventListener eventListener;
+
+    private ConcurrentHashMap<String, 
org.apache.pulsar.client.api.Consumer<byte[]>> consumerMap = new 
ConcurrentHashMap<>();
+
+    /**
+     * Unified configuration class corresponding to pulsar-client.properties
+     */
+    private ClientConfiguration clientConfiguration;
+
+    @Override
+    public void init(Properties properties) throws Exception {
+        this.properties = properties;
+        String token = properties.getProperty(Constants.CONSUMER_TOKEN);
+
+        try {
+            ClientBuilder clientBuilder = PulsarClient.builder()
+                .serviceUrl(clientConfiguration.getServiceAddr());
+
+            if (clientConfiguration.getAuthPlugin() != null) {
+                Preconditions.checkNotNull(clientConfiguration.getAuthParams(),
+                    "Authentication Enabled in pulsar cluster, Please set 
authParams in pulsar-client.properties");
+                clientBuilder.authentication(
+                    clientConfiguration.getAuthPlugin(),
+                    clientConfiguration.getAuthParams()
+                );
+            }
+            if (StringUtils.isNotBlank(token)) {
+                clientBuilder.authentication(
+                    AuthenticationFactory.token(token)
+                );
+            }
+
+            this.pulsarClient = clientBuilder.build();
+        } catch (Exception ex) {
+            throw new StorageConnectorRuntimeException(
+              String.format("Failed to connect pulsar with exception: %s", 
ex.getMessage()));
+        }
+    }
+
+    @Override
+    public void start() {
+        this.started.compareAndSet(false, true);
+    }
+
+    @Override
+    public void subscribe(String topic) throws Exception {
+        String subTopic = clientConfiguration.getTopicPrefix() + topic;
+        if (pulsarClient == null) {
+            throw new StorageConnectorRuntimeException(
+                String.format("Cann't find the pulsar client for topic: %s", 
subTopic));
+        }
+
+        EventMeshAsyncConsumeContext consumeContext = new 
EventMeshAsyncConsumeContext() {
+            @Override
+            public void commit(EventMeshAction action) {
+                log.debug("message action: {} for topic: {}", action.name(), 
subTopic);
+            }
+        };
+
+        SubscriptionType type = SubscriptionType.Shared;
+
+        String consumerKey = topic + PulsarConstant.KEY_SEPARATOR + 
properties.getProperty(Constants.CONSUMER_GROUP)
+            + PulsarConstant.KEY_SEPARATOR + 
properties.getProperty(Constants.CLIENT_ADDRESS);
+        org.apache.pulsar.client.api.Consumer<byte[]> consumer = 
pulsarClient.newConsumer()
+            .topic(subTopic)
+            .subscriptionName(properties.getProperty(Constants.CONSUMER_GROUP))
+            .subscriptionMode(SubscriptionMode.Durable)
+            .subscriptionType(type)
+            .messageListener(
+                (MessageListener<byte[]>) (ackConsumer, msg) -> {
+                    CloudEvent cloudEvent = EventFormatProvider
+                        .getInstance()
+                        .resolveFormat(JsonFormat.CONTENT_TYPE)
+                        .deserialize(msg.getData());
+                    eventListener.consume(cloudEvent, consumeContext);
+                    try {
+                        ackConsumer.acknowledge(msg);
+                    } catch (PulsarClientException ex) {
+                        throw new StorageConnectorRuntimeException(
+                            String.format("Failed to unsubscribe the topic:%s 
with exception: %s", subTopic, ex.getMessage()));
+                    } catch (EventDeserializationException ex) {
+                        log.warn("The Message isn't json format, with 
exception:{}", ex.getMessage());
+                    }
+                }).subscribe();
+
+        consumerMap.putIfAbsent(consumerKey, consumer);
+
+    }
+
+    @Override
+    public void unsubscribe(String topic) {
+        try {
+            String consumerKey = topic + PulsarConstant.KEY_SEPARATOR + 
properties.getProperty(Constants.CONSUMER_GROUP)
+                + PulsarConstant.KEY_SEPARATOR + 
properties.getProperty(Constants.CLIENT_ADDRESS);
+            org.apache.pulsar.client.api.Consumer<byte[]> consumer = 
consumerMap.get(consumerKey);
+            consumer.unsubscribe();
+            consumerMap.remove(consumerKey);
+        } catch (PulsarClientException ex) {
+            throw new StorageConnectorRuntimeException(
+                String.format("Failed to unsubscribe the topic:%s with 
exception: %s", topic, ex.getMessage()));
+        }
+    }
+
+    @Override
+    public void updateOffset(List<CloudEvent> cloudEvents, AbstractContext 
context) {
+    }
+
+    @Override
+    public void registerEventListener(EventListener listener) {
+        this.eventListener = listener;
+    }
+
+    @Override
+    public boolean isStarted() {
+        return this.started.get();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return !this.isStarted();
+    }
+
+    @Override
+    public void shutdown() {
+        this.started.compareAndSet(true, false);
+        try {
+
+            consumerMap.forEach((key, consumer) -> {
+                try {
+                    consumer.close();
+                } catch (PulsarClientException e) {
+                    throw new StorageConnectorRuntimeException(
+                        String.format("Failed to close the pulsar consumer 
with exception: %s", e.getMessage()));
+                }
+            });
+            this.pulsarClient.close();
+            consumerMap.clear();
+        } catch (PulsarClientException ex) {
+            throw new StorageConnectorRuntimeException(
+                String.format("Failed to close the pulsar client with 
exception: %s", ex.getMessage()));
+        }
+    }
+
+    public ClientConfiguration getClientConfiguration() {
+        return this.clientConfiguration;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/AbstractProducer.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/AbstractProducer.java
new file mode 100644
index 000000000..4137f8620
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/AbstractProducer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.producer;
+
+import org.apache.eventmesh.api.exception.StorageConnectorRuntimeException;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+import io.cloudevents.CloudEvent;
+
+public abstract class AbstractProducer {
+
+    protected final AtomicBoolean started = new AtomicBoolean(false);
+    final Properties properties;
+
+    AbstractProducer(final Properties properties) {
+        this.properties = properties;
+    }
+
+    public Properties properties() {
+        return this.properties;
+    }
+
+    StorageConnectorRuntimeException checkProducerException(CloudEvent 
cloudEvent, Throwable e) {
+        if (cloudEvent.getData() == null) {
+            return new 
StorageConnectorRuntimeException(String.format("CloudEvent message data does 
not exist, %s", e.getMessage()));
+        }
+        return new StorageConnectorRuntimeException(String.format("Unknown 
connector runtime exception, %s", e.getMessage()));
+    }
+
+    public boolean isStarted() {
+        return this.started.get();
+    }
+
+    public boolean isClosed() {
+        return !this.isStarted();
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/ProducerImpl.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/ProducerImpl.java
new file mode 100644
index 000000000..343634393
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/ProducerImpl.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.producer;
+
+import org.apache.eventmesh.api.SendCallback;
+import org.apache.eventmesh.storage.pulsar.client.PulsarClientWrapper;
+import org.apache.eventmesh.storage.pulsar.config.ClientConfiguration;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.cloudevents.CloudEvent;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ProducerImpl extends AbstractProducer {
+
+    private final AtomicBoolean started = new AtomicBoolean(false);
+
+    private ClientConfiguration config;
+    private PulsarClientWrapper pulsarClient;
+
+    public ProducerImpl(final Properties properties, ClientConfiguration 
config) {
+        this(properties);
+        setConfig(config);
+    }
+
+    public ProducerImpl(final Properties properties) {
+        super(properties);
+    }
+
+    public void publish(CloudEvent cloudEvent, SendCallback sendCallback) {
+        this.pulsarClient.publish(cloudEvent, sendCallback);
+    }
+
+    public void init(Properties properties) {
+        new ProducerImpl(properties);
+    }
+
+    public void start() {
+        this.started.compareAndSet(false, true);
+        this.pulsarClient = new PulsarClientWrapper(config, properties);
+    }
+
+    public void shutdown() {
+        try {
+            this.started.compareAndSet(true, false);
+            this.pulsarClient.shutdown();
+        } catch (Exception ignored) {
+            // ignored
+        }
+    }
+
+    @Override
+    public boolean isStarted() {
+        return this.started.get();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return !this.isStarted();
+    }
+
+    public void setConfig(ClientConfiguration config) {
+        this.config = config;
+    }
+}
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/PulsarProducerImpl.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/PulsarProducerImpl.java
new file mode 100644
index 000000000..11dfbc3f2
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/producer/PulsarProducerImpl.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.producer;
+
+import org.apache.eventmesh.api.RequestReplyCallback;
+import org.apache.eventmesh.api.SendCallback;
+import org.apache.eventmesh.api.exception.StorageConnectorRuntimeException;
+import org.apache.eventmesh.api.producer.Producer;
+import org.apache.eventmesh.common.config.Config;
+import org.apache.eventmesh.storage.pulsar.config.ClientConfiguration;
+
+import java.util.Properties;
+
+import io.cloudevents.CloudEvent;
+
+@Config(field = "clientConfiguration")
+public class PulsarProducerImpl implements Producer {
+
+    private ProducerImpl producer;
+
+    /**
+     * Unified configuration class corresponding to pulsar-client.properties
+     */
+    private ClientConfiguration clientConfiguration;
+
+    @Override
+    public synchronized void init(Properties properties) {
+        producer = new ProducerImpl(properties, clientConfiguration);
+    }
+
+    @Override
+    public void publish(CloudEvent cloudEvent, SendCallback sendCallback) 
throws Exception {
+        producer.publish(cloudEvent, sendCallback);
+    }
+
+    @Override
+    public boolean isStarted() {
+        return producer.isStarted();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return producer.isClosed();
+    }
+
+    @Override
+    public void start() {
+        producer.start();
+    }
+
+    @Override
+    public void shutdown() {
+        producer.shutdown();
+    }
+
+    @Override
+    public void sendOneway(CloudEvent cloudEvent) {
+        throw new StorageConnectorRuntimeException("SendOneWay is not 
supported");
+    }
+
+    @Override
+    public void request(CloudEvent cloudEvent, RequestReplyCallback 
rrCallback, long timeout) throws Exception {
+        throw new StorageConnectorRuntimeException("Request is not supported");
+    }
+
+    @Override
+    public boolean reply(CloudEvent cloudEvent, SendCallback sendCallback) 
throws Exception {
+        throw new StorageConnectorRuntimeException("Reply is not supported");
+    }
+
+    @Override
+    public void checkTopicExist(String topic) throws Exception {
+        throw new StorageConnectorRuntimeException("CheckTopicExist is not 
supported");
+    }
+
+    @Override
+    public void setExtFields() {
+        throw new StorageConnectorRuntimeException("SetExtFields is not 
supported");
+    }
+
+    public ClientConfiguration getClientConfiguration() {
+        return this.clientConfiguration;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/storage/StorageResourceServicePulsarImpl.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/storage/StorageResourceServicePulsarImpl.java
new file mode 100644
index 000000000..7b09cc685
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/storage/StorageResourceServicePulsarImpl.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.storage;
+
+import org.apache.eventmesh.api.storage.StorageResourceService;
+
+public class StorageResourceServicePulsarImpl implements 
StorageResourceService {
+
+    @Override
+    public void init() throws Exception {
+
+    }
+
+    @Override
+    public void release() throws Exception {
+
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/utils/CloudEventUtils.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/utils/CloudEventUtils.java
new file mode 100644
index 000000000..71d40d29c
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/java/org/apache/eventmesh/storage/pulsar/utils/CloudEventUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.utils;
+
+import org.apache.eventmesh.api.SendResult;
+
+import io.cloudevents.CloudEvent;
+
+public class CloudEventUtils {
+
+    public static SendResult convertSendResult(CloudEvent cloudEvent) {
+        SendResult sendResult = new SendResult();
+        sendResult.setTopic(cloudEvent.getSubject());
+        sendResult.setMessageId(cloudEvent.getId());
+        return sendResult;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.Consumer
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.Consumer
new file mode 100644
index 000000000..6716ddce4
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.Consumer
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+pulsar=org.apache.eventmesh.storage.pulsar.consumer.PulsarConsumerImpl
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.Producer
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.Producer
new file mode 100644
index 000000000..235173dea
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.Producer
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+pulsar=org.apache.eventmesh.storage.pulsar.producer.PulsarProducerImpl
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.storage.StorageResourceService
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.storage.StorageResourceService
new file mode 100644
index 000000000..4b210007a
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.storage.StorageResourceService
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+pulsar=org.apache.eventmesh.storage.pulsar.storage.StorageResourceServicePulsarImpl
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/pulsar-client.properties
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/pulsar-client.properties
new file mode 100644
index 000000000..cea69024e
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/main/resources/pulsar-client.properties
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+eventMesh.server.pulsar.service=127.0.0.1:6650
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/test/java/org/apache/eventmesh/storage/pulsar/config/ClientConfigurationTest.java
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/test/java/org/apache/eventmesh/storage/pulsar/config/ClientConfigurationTest.java
new file mode 100644
index 000000000..40ffb3fdc
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/test/java/org/apache/eventmesh/storage/pulsar/config/ClientConfigurationTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.storage.pulsar.config;
+
+import org.apache.eventmesh.api.factory.StoragePluginFactory;
+import org.apache.eventmesh.storage.pulsar.consumer.PulsarConsumerImpl;
+import org.apache.eventmesh.storage.pulsar.producer.PulsarProducerImpl;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClientConfigurationTest {
+
+    @Test
+    public void getConfigWhenPulsarConsumerInit() {
+        PulsarConsumerImpl consumer = (PulsarConsumerImpl) 
StoragePluginFactory.getMeshMQPushConsumer("pulsar");
+
+        ClientConfiguration config = consumer.getClientConfiguration();
+        assertConfig(config);
+    }
+
+    @Test
+    public void getConfigWhenPulsarProducerInit() {
+        PulsarProducerImpl producer = (PulsarProducerImpl) 
StoragePluginFactory.getMeshMQProducer("pulsar");
+
+        ClientConfiguration config = producer.getClientConfiguration();
+        assertConfig(config);
+    }
+
+    private void assertConfig(ClientConfiguration config) {
+        Assert.assertEquals(config.getServiceAddr(), "127.0.0.1:6650");
+        Assert.assertEquals(config.getAuthPlugin(), "authPlugin-success!!!");
+        Assert.assertEquals(config.getAuthParams(), "authParams-success!!!");
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pulsar/src/test/resources/pulsar-client.properties
 
b/eventmesh-storage/eventmesh-storage-pulsar/src/test/resources/pulsar-client.properties
new file mode 100644
index 000000000..645a5edb6
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pulsar/src/test/resources/pulsar-client.properties
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+eventMesh.server.pulsar.service=127.0.0.1:6650
+eventMesh.server.pulsar.authPlugin=authPlugin-success!!!
+eventMesh.server.pulsar.authParams=authParams-success!!!
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 3449270a4..826b0a8bb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,6 +26,7 @@ include 'eventmesh-spi'
 include 'eventmesh-storage:eventmesh-storage-api'
 include 'eventmesh-storage:eventmesh-storage-standalone'
 include 'eventmesh-storage:eventmesh-storage-kafka'
+include 'eventmesh-storage:eventmesh-storage-pulsar'
 include 'eventmesh-storage:eventmesh-storage-pravega'
 include 'eventmesh-storage:eventmesh-storage-redis'
 include 'eventmesh-storage:eventmesh-storage-rocketmq'


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to