From 72fb7fb76b2081348f337beeda16c856c7fb1ca6 Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Mon, 7 Aug 2023 14:08:31 +0800
Subject: [PATCH 1/7] [NEW] Added maven-ci.yml [NEW] Added WatcherConstant.java
[NEW] Added LettuceRedisWatcher.java [NEW] Added LettuceSubscriber.java [NEW]
Added LettuceSubThread.java [NEW] Added LettuceRedisWatcherTest.java [UPDATE]
Updated .gitignore [NEW] Added .releaserc.json [NEW] Added maven-settings.xml
[NEW] Added pom.xml [UPDATE] Updated README.md
---
.github/workflows/maven-ci.yml | 59 ++++
.gitignore | 5 +
.releaserc.json | 17 +
README.md | 27 +-
maven-settings.xml | 22 ++
pom.xml | 291 ++++++++++++++++++
.../watcher/lettuce/LettuceRedisWatcher.java | 156 ++++++++++
.../watcher/lettuce/LettuceSubThread.java | 124 ++++++++
.../watcher/lettuce/LettuceSubscriber.java | 27 ++
.../lettuce/constants/WatcherConstant.java | 19 ++
.../casbin/test/LettuceRedisWatcherTest.java | 63 ++++
11 files changed, 809 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/maven-ci.yml
create mode 100644 .releaserc.json
create mode 100644 maven-settings.xml
create mode 100644 pom.xml
create mode 100644 src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java
create mode 100644 src/main/java/org/casbin/watcher/lettuce/LettuceSubThread.java
create mode 100644 src/main/java/org/casbin/watcher/lettuce/LettuceSubscriber.java
create mode 100644 src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java
create mode 100644 src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml
new file mode 100644
index 0000000..12d6f9f
--- /dev/null
+++ b/.github/workflows/maven-ci.yml
@@ -0,0 +1,59 @@
+name: build
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ services:
+ redis:
+ image: redis
+ ports:
+ - 6378:6378
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: '0'
+
+ - name: Set up Redis with password
+ uses: getong/redis-action@v1
+ with:
+ redis version: 'latest'
+ host port: 6379
+ container port: 6379
+ redis password: 'foobared'
+
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ server-username: OSSRH_USERNAME
+ server-password: OSSRH_PASSWORD
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+ gpg-passphrase: GPG_PASSPHRASE
+
+ - name: Build with Maven
+ run: mvn clean test cobertura:cobertura
+
+ - name: Codecov
+ uses: codecov/codecov-action@v1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: 16
+
+ - name: Sematic Release
+ run: |
+ npm install -g @conveyal/maven-semantic-release semantic-release
+ semantic-release --prepare @conveyal/maven-semantic-release --publish @semantic-release/github,@conveyal/maven-semantic-release --verify-conditions @semantic-release/github,@conveyal/maven-semantic-release --verify-release @conveyal/maven-semantic-release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+ OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+ OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
diff --git a/.gitignore b/.gitignore
index 524f096..d909bae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,12 @@
*.zip
*.tar.gz
*.rar
+*.iml
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+.idea
+target/
+out/
\ No newline at end of file
diff --git a/.releaserc.json b/.releaserc.json
new file mode 100644
index 0000000..8c262e0
--- /dev/null
+++ b/.releaserc.json
@@ -0,0 +1,17 @@
+{
+ "debug": true,
+ "dryRun": false,
+ "branches": [
+ "+([0-9])?(.{+([0-9]),x}).x",
+ "master",
+ {
+ "name": "beta",
+ "prerelease": true
+ }
+ ],
+ "plugins": [
+ "@semantic-release/commit-analyzer",
+ "@semantic-release/release-notes-generator",
+ "@semantic-release/github"
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 99ccd63..6ada743 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,26 @@
-# lettuce-redis-watcher
\ No newline at end of file
+# lettuce-redis-watcher
+---
+
+[![GitHub Actions](https://github.com/jcasbin/lettuce-redis-watcher/actions/workflows/maven-ci.yml/badge.svg)](https://github.com/jcasbin/lettuce-redis-watcher/actions/workflows/maven-ci.yml)
+![License](https://img.shields.io/github/license/jcasbin/lettuce-redis-watcher)
+[![Javadoc](https://javadoc.io/badge2/org.casbin/jcasbin-lettuce-redis-watcher/javadoc.svg)](https://javadoc.io/doc/org.casbin/jcasbin-lettuce-redis-watcher)
+[![codecov](https://codecov.io/gh/jcasbin/lettuce-redis-watcher/branch/master/graph/badge.svg?token=ENt9xr4nFg)](https://codecov.io/gh/jcasbin/lettuce-redis-watcher)
+[![codebeat badge](https://codebeat.co/badges/8b3da1c4-3a61-4123-a3d4-002b2598a297)](https://codebeat.co/projects/github-com-jcasbin-lettuce-redis-watcher-master)
+[![Maven Central](https://img.shields.io/maven-central/v/org.casbin/jcasbin-lettuce-redis-watcher.svg)](https://mvnrepository.com/artifact/org.casbin/jcasbin-lettuce-redis-watcher/latest)
+[![Release](https://img.shields.io/github/release/jcasbin/lettuce-redis-watcher.svg)](https://github.com/jcasbin/lettuce-redis-watcher/releases/latest)
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby)
+
+---
+[![Security Status](https://www.murphysec.com/platform3/v31/badge/1688427475171692544.svg)](https://www.murphysec.com/console/report/1688411419949424640/1688427475171692544)
+
+
+Lettuce Redis Watcher is a [Redis](http://redis.io) watcher for [jCasbin](https://github.com/casbin/jcasbin).
+
+## Getting Help
+
+- [jCasbin](https://github.com/casbin/jCasbin)
+- [Lettuce](https://lettuce.io)
+
+## License
+
+This project is under Apache 2.0 License. See the [LICENSE](https://github.com/jcasbin/lettuce-redis-watcher/blob/master/LICENSE) file for the full license text.
\ No newline at end of file
diff --git a/maven-settings.xml b/maven-settings.xml
new file mode 100644
index 0000000..410aee6
--- /dev/null
+++ b/maven-settings.xml
@@ -0,0 +1,22 @@
+
+
+
+ ossrh
+ ${OSSRH_USERNAME}
+ ${OSSRH_PASSWORD}
+
+
+
+
+ ossrh
+
+ true
+
+
+ gpg
+ ${GPG_KEY_NAME}
+ ${GPG_PASSPHRASE}
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..387332b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,291 @@
+
+
+ 4.0.0
+ org.casbin
+ jcasbin-lettuce-redis-watcher
+ 1.0.0
+
+
+ org.sonatype.oss
+ oss-parent
+ 7
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+ https://github.com/jcasbin/lettuce-redis-watcher
+ scm:git@github.com:jcasbin/lettuce-redis-watcher.git
+ scm:git:https://github.com/jcasbin/lettuce-redis-watcher.git
+
+
+
+ Shingmo Yeung
+ 525032303@qq.com
+ https://github.com/ShingmoYeung
+
+
+
+ Github
+ https://github.com/jcasbin/lettuce-redis-watcher/issues
+
+
+
+
+ ossrh
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+ ossrh
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+
+ 1.8
+ UTF-8
+ ${java.version}
+ ${java.version}
+ ${source.encoding}
+ 3.3.0
+ 3.11.0
+ 3.1.0
+ 3.5.0
+ 2.7
+ 1.6.13
+ UTF-8
+ ${source.encoding}
+
+ 1.35.0
+ 6.2.5.RELEASE
+ 2.11.1
+ 3.13.0
+ 2.0.7
+ 5.3.3
+ 4.1.96.Final
+ 4.13.2
+
+
+
+
+
+ org.casbin
+ jcasbin
+ ${jcasbin.version}
+
+
+ io.lettuce
+ lettuce-core
+ ${lettuce-core.version}
+
+
+ org.apache.commons
+ commons-pool2
+ ${commons-pool2.version}
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ com.googlecode.aviator
+ aviator
+ ${aviator.version}
+
+
+ io.netty
+ netty-common
+ ${netty.version}
+
+
+ io.netty
+ netty-handler
+ ${netty.version}
+
+
+ io.netty
+ netty-transport
+ ${netty.version}
+
+
+ io.netty
+ netty-resolver-dns
+ ${netty.version}
+ true
+
+
+ io.netty
+ netty-transport-native-epoll
+ ${netty.version}
+ linux-x86_64
+ true
+
+
+ io.netty
+ netty-transport-native-kqueue
+ ${netty.version}
+ osx-x86_64
+ true
+
+
+ junit
+ junit
+ ${junit.version}
+
+
+
+
+
+
+ org.casbin
+ jcasbin
+ true
+
+
+ io.lettuce
+ lettuce-core
+ true
+
+
+ org.apache.commons
+ commons-pool2
+ true
+
+
+ org.apache.commons
+ commons-lang3
+ true
+
+
+ org.slf4j
+ slf4j-api
+ true
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven-gpg-plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ false
+
+
+ notnull
+ a
+ Not null
+
+
+ default
+ a
+ Default:
+
+
+
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ ${nexus-staging-maven-plugin.version}
+ true
+
+ ossrh
+ https://oss.sonatype.org/
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ ${java.version}
+
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ ${cobertura-maven-plugin.version}
+
+
+ html
+ xml
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java b/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java
new file mode 100644
index 0000000..6c51ad0
--- /dev/null
+++ b/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java
@@ -0,0 +1,156 @@
+package org.casbin.watcher.lettuce;
+
+import io.lettuce.core.AbstractRedisClient;
+import io.lettuce.core.ClientOptions;
+import io.lettuce.core.RedisClient;
+import io.lettuce.core.RedisURI;
+import io.lettuce.core.cluster.ClusterClientOptions;
+import io.lettuce.core.cluster.RedisClusterClient;
+import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
+import io.lettuce.core.resource.ClientResources;
+import io.lettuce.core.resource.DefaultClientResources;
+import org.apache.commons.lang3.StringUtils;
+import org.casbin.jcasbin.persist.Watcher;
+import org.casbin.watcher.lettuce.constants.WatcherConstant;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class LettuceRedisWatcher implements Watcher {
+ private final String localId;
+ private final String redisChannelName;
+ private final AbstractRedisClient abstractRedisClient;
+ private LettuceSubThread lettuceSubThread;
+ private Runnable updateCallback;
+
+ /**
+ * Constructor
+ *
+ * @param redisIp Redis IP
+ * @param redisPort Redis Port
+ * @param redisChannelName Redis Channel
+ * @param timeout Redis Timeout
+ * @param password Redis Password
+ * @param type Redis Type (standalone | cluster)
+ */
+ public LettuceRedisWatcher(String redisIp, int redisPort, String redisChannelName, int timeout, String password, String type) {
+ this.abstractRedisClient = this.getLettuceRedisClient(redisIp, redisPort, password, timeout, type);
+ this.localId = UUID.randomUUID().toString();
+ this.redisChannelName = redisChannelName;
+ this.startSub();
+ }
+
+ /**
+ * Constructor
+ *
+ * @param redisIp Redis IP
+ * @param redisPort Redis Port
+ * @param redisChannelName Redis Channel
+ * @param type Redis Type (standalone | cluster)
+ */
+ public LettuceRedisWatcher(String redisIp, int redisPort, String redisChannelName, String type) {
+ this(redisIp, redisPort, redisChannelName, 2000, null, type);
+ }
+
+ @Override
+ public void setUpdateCallback(Runnable runnable) {
+ this.updateCallback = runnable;
+ lettuceSubThread.setUpdateCallback(runnable);
+ }
+
+ @Override
+ public void setUpdateCallback(Consumer consumer) {
+ this.lettuceSubThread.setUpdateCallback(consumer);
+ }
+
+ @Override
+ public void update() {
+ try (StatefulRedisPubSubConnection statefulRedisPubSubConnection =
+ this.getStatefulRedisPubSubConnection(this.abstractRedisClient)) {
+ if (statefulRedisPubSubConnection.isOpen()) {
+ String msg = "Casbin policy has a new version from redis watcher: ".concat(this.localId);
+ statefulRedisPubSubConnection.async().publish(this.redisChannelName, msg);
+ }
+ }
+ }
+
+ private void startSub() {
+ this.lettuceSubThread = new LettuceSubThread(this.abstractRedisClient, this.redisChannelName, this.updateCallback);
+ this.lettuceSubThread.start();
+ }
+
+ /**
+ * Initialize the Redis Client
+ *
+ * @param host Redis Host
+ * @param port Redis Port
+ * @param password Redis Password
+ * @param timeout Redis Timeout
+ * @param type Redis Type (standalone | cluster) default:standalone
+ * @return AbstractRedisClient
+ */
+ private AbstractRedisClient getLettuceRedisClient(String host, int port, String password, int timeout, String type) {
+ // todo default standalone ?
+ // type = StringUtils.isEmpty(type) ? WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE : type;
+ if (StringUtils.isNotEmpty(type) && StringUtils.equalsAnyIgnoreCase(type,
+ WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE, WatcherConstant.LETTUCE_REDIS_TYPE_CLUSTER)) {
+ RedisURI redisUri = null;
+ if (StringUtils.isNotEmpty(password)) {
+ redisUri = RedisURI.builder()
+ .withHost(host)
+ .withPort(port)
+ .withPassword(password.toCharArray())
+ .withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
+ .build();
+ } else {
+ redisUri = RedisURI.builder()
+ .withHost(host)
+ .withPort(port)
+ .withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
+ .build();
+ }
+ ClientResources clientResources = DefaultClientResources.builder()
+ .ioThreadPoolSize(4)
+ .computationThreadPoolSize(4)
+ .build();
+ if (StringUtils.equalsIgnoreCase(type, WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE)) {
+ // standalone
+ ClientOptions clientOptions = ClientOptions.builder()
+ .autoReconnect(true)
+ .pingBeforeActivateConnection(true)
+ .build();
+ RedisClient redisClient = RedisClient.create(clientResources, redisUri);
+ redisClient.setOptions(clientOptions);
+ return redisClient;
+ } else {
+ // cluster
+ ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
+ .autoReconnect(true)
+ .pingBeforeActivateConnection(true)
+ .validateClusterNodeMembership(true)
+ .build();
+ RedisClusterClient redisClusterClient = RedisClusterClient.create(clientResources, redisUri);
+ redisClusterClient.setOptions(clusterClientOptions);
+ return redisClusterClient;
+ }
+ } else {
+ throw new IllegalArgumentException("Redis-Type is required and can only be [standalone] or [cluster]");
+ }
+ }
+
+ /**
+ * Get Redis PubSub Connection
+ *
+ * @param abstractRedisClient Redis Client
+ * @return StatefulRedisPubSubConnection
+ */
+ private StatefulRedisPubSubConnection getStatefulRedisPubSubConnection(AbstractRedisClient abstractRedisClient) {
+ if (abstractRedisClient instanceof RedisClient) {
+ return ((RedisClient) abstractRedisClient).connectPubSub();
+ } else {
+ return ((RedisClusterClient) abstractRedisClient).connectPubSub();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/casbin/watcher/lettuce/LettuceSubThread.java b/src/main/java/org/casbin/watcher/lettuce/LettuceSubThread.java
new file mode 100644
index 0000000..62d1336
--- /dev/null
+++ b/src/main/java/org/casbin/watcher/lettuce/LettuceSubThread.java
@@ -0,0 +1,124 @@
+package org.casbin.watcher.lettuce;
+
+import io.lettuce.core.AbstractRedisClient;
+import io.lettuce.core.RedisClient;
+import io.lettuce.core.cluster.RedisClusterClient;
+import io.lettuce.core.pubsub.RedisPubSubListener;
+import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.function.Consumer;
+
+public class LettuceSubThread extends Thread {
+ private static final Logger logger = LoggerFactory.getLogger(LettuceSubThread.class);
+ private final String channel;
+ private final LettuceSubscriber lettuceSubscriber;
+ private final AbstractRedisClient abstractRedisClient;
+ private StatefulRedisPubSubConnection statefulRedisPubSubConnection;
+
+ /**
+ * Construction method
+ *
+ * @param abstractRedisClient abstractRedisClient
+ * @param channel channel
+ * @param updateCallback updateCallback
+ */
+ public LettuceSubThread(AbstractRedisClient abstractRedisClient, String channel, Runnable updateCallback) {
+ super("LettuceSubThread");
+ this.channel = channel;
+ this.abstractRedisClient = abstractRedisClient;
+ lettuceSubscriber = new LettuceSubscriber(updateCallback);
+ }
+
+ /**
+ * set runnable
+ *
+ * @param runnable runnable
+ */
+ public void setUpdateCallback(Runnable runnable) {
+ lettuceSubscriber.setUpdateCallback(runnable);
+ }
+
+ /**
+ * set consumer
+ *
+ * @param consumer runnable
+ */
+ public void setUpdateCallback(Consumer consumer) {
+ lettuceSubscriber.setUpdateCallback(consumer);
+ }
+
+ @Override
+ public void run() {
+ try {
+ this.statefulRedisPubSubConnection = this.getStatefulRedisPubSubConnection(this.abstractRedisClient);
+ if (this.statefulRedisPubSubConnection.isOpen()) {
+ this.statefulRedisPubSubConnection.addListener(new RedisPubSubListener() {
+ @Override
+ public void unsubscribed(String channel, long count) {
+ logger.info("[unsubscribed] {}", channel);
+ }
+
+ @Override
+ public void subscribed(String channel, long count) {
+ logger.info("[subscribed] {}", channel);
+ }
+
+ @Override
+ public void punsubscribed(String pattern, long count) {
+ logger.info("[punsubscribed] {}", pattern);
+ }
+
+ @Override
+ public void psubscribed(String pattern, long count) {
+ logger.info("[psubscribed] {}", pattern);
+ }
+
+ @Override
+ public void message(String pattern, String channel, String message) {
+ logger.info("[message] {} -> {} -> {}", pattern, channel, message);
+ lettuceSubscriber.onMessage(channel, message);
+ }
+
+ @Override
+ public void message(String channel, String message) {
+ logger.info("[message] {} -> {}", channel, message);
+ lettuceSubscriber.onMessage(channel, message);
+ }
+ });
+ this.statefulRedisPubSubConnection.async().subscribe(this.channel);
+
+ Thread.sleep(500);
+ }
+ } catch (Exception e) {
+ logger.error("error message {}", e.getMessage());
+ this.close(this.statefulRedisPubSubConnection);
+ }
+ }
+
+ /**
+ * Close Redis PubSub Connection
+ *
+ * @param statefulRedisPubSubConnection Redis PubSub Connection
+ */
+ private void close(StatefulRedisPubSubConnection statefulRedisPubSubConnection) {
+ if (statefulRedisPubSubConnection.isOpen()) {
+ statefulRedisPubSubConnection.closeAsync();
+ }
+ }
+
+ /**
+ * Get Redis PubSub Connection
+ *
+ * @param abstractRedisClient Redis Client
+ * @return StatefulRedisPubSubConnection
+ */
+ private StatefulRedisPubSubConnection getStatefulRedisPubSubConnection(AbstractRedisClient abstractRedisClient) {
+ if (abstractRedisClient instanceof RedisClient) {
+ return ((RedisClient) abstractRedisClient).connectPubSub();
+ } else {
+ return ((RedisClusterClient) abstractRedisClient).connectPubSub();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/casbin/watcher/lettuce/LettuceSubscriber.java b/src/main/java/org/casbin/watcher/lettuce/LettuceSubscriber.java
new file mode 100644
index 0000000..286a032
--- /dev/null
+++ b/src/main/java/org/casbin/watcher/lettuce/LettuceSubscriber.java
@@ -0,0 +1,27 @@
+package org.casbin.watcher.lettuce;
+
+import java.util.function.Consumer;
+
+public class LettuceSubscriber {
+ private Runnable runnable;
+ private Consumer consumer;
+
+ public LettuceSubscriber(Runnable updateCallback) {
+ this.runnable = updateCallback;
+ }
+
+ public void setUpdateCallback(Runnable runnable){
+ this.runnable = runnable;
+ }
+
+ public void setUpdateCallback(Consumer consumer) {
+ this.consumer = consumer;
+ }
+
+ public void onMessage(String channel, String message) {
+ runnable.run();
+ if (this.consumer != null) {
+ this.consumer.accept("Channel: " + channel + " Message: " + message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java b/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java
new file mode 100644
index 0000000..0875c08
--- /dev/null
+++ b/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java
@@ -0,0 +1,19 @@
+package org.casbin.watcher.lettuce.constants;
+
+/**
+ * Created by IntelliJ IDEA 2023.
+ * FileName: WatcherConstant.java
+ *
+ * @author shingmoyeung
+ * @since 2023/8/5 21:02
+ * @version 1.0
+ * To change this template use File Or Preferences | Settings | Editor | File and Code Templates.
+ * File Description: Redis Watcher Constant
+ */
+public class WatcherConstant {
+ /**
+ * Redis Type
+ */
+ public static final String LETTUCE_REDIS_TYPE_STANDALONE = "standalone";
+ public static final String LETTUCE_REDIS_TYPE_CLUSTER = "cluster";
+}
\ No newline at end of file
diff --git a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
new file mode 100644
index 0000000..56ea5a2
--- /dev/null
+++ b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
@@ -0,0 +1,63 @@
+package org.casbin.test;
+
+import org.casbin.jcasbin.main.Enforcer;
+import org.casbin.watcher.lettuce.LettuceRedisWatcher;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LettuceRedisWatcherTest {
+ /**
+ * LettuceRedisWatcher
+ */
+ private LettuceRedisWatcher lettuceRedisWatcher;
+
+ /**
+ * You should replace the initWatcher() method's content with your own Redis instance.
+ */
+ @Before
+ public void initWatcher() {
+ String redisTopic = "jcasbin-topic";
+ this.lettuceRedisWatcher = new LettuceRedisWatcher("127.0.0.1", 6379, redisTopic, 2000, "foobared", "standalone");
+ Enforcer enforcer = new Enforcer();
+ enforcer.setWatcher(this.lettuceRedisWatcher);
+ }
+
+ @Test
+ public void testUpdate() throws InterruptedException {
+ this.initWatcher();
+ this.lettuceRedisWatcher.update();
+ Thread.sleep(100);
+ }
+
+ @Test
+ public void testConsumerCallback() throws InterruptedException {
+ this.initWatcher();
+ while (true) {
+ this.lettuceRedisWatcher.setUpdateCallback((s) -> System.out.println(s));
+ this.lettuceRedisWatcher.update();
+ Thread.sleep(500);
+ }
+ }
+
+ @Test
+ public void testConnectWatcherWithoutPassword() {
+ String redisTopic = "jcasbin-topic";
+ LettuceRedisWatcher lettuceRedisWatcherWithoutPassword = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "standalone");
+ Assert.assertNotNull(lettuceRedisWatcherWithoutPassword);
+ }
+
+ @Test
+ public void testConnectWatcherWithType() {
+ String redisTopic = "jcasbin-topic";
+ Assert.assertThrows(IllegalArgumentException.class, () -> {
+ new LettuceRedisWatcher("192.168.3.244", 6379, redisTopic, "sentinel");
+ });
+
+ LettuceRedisWatcher lettuceRedisWatcherStandalone = new LettuceRedisWatcher("192.168.3.244", 6378, redisTopic, "standalone");
+ Assert.assertNotNull(lettuceRedisWatcherStandalone);
+
+ LettuceRedisWatcher lettuceRedisWatcherCluster = new LettuceRedisWatcher("192.168.3.244", 6378, redisTopic, "cluster");
+ Assert.assertNotNull(lettuceRedisWatcherCluster);
+ }
+}
\ No newline at end of file
From 5569adf63e00e16d60d60aa75580df8d4292965e Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Mon, 7 Aug 2023 14:17:24 +0800
Subject: [PATCH 2/7] Refactor LettuceRedisWatcherTest.java to use local IP
address instead of remote IP address for testing.
---
src/test/java/org/casbin/test/LettuceRedisWatcherTest.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
index 56ea5a2..cf56ff2 100644
--- a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
+++ b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
@@ -51,13 +51,13 @@ public void testConnectWatcherWithoutPassword() {
public void testConnectWatcherWithType() {
String redisTopic = "jcasbin-topic";
Assert.assertThrows(IllegalArgumentException.class, () -> {
- new LettuceRedisWatcher("192.168.3.244", 6379, redisTopic, "sentinel");
+ new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "sentinel");
});
- LettuceRedisWatcher lettuceRedisWatcherStandalone = new LettuceRedisWatcher("192.168.3.244", 6378, redisTopic, "standalone");
+ LettuceRedisWatcher lettuceRedisWatcherStandalone = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "standalone");
Assert.assertNotNull(lettuceRedisWatcherStandalone);
- LettuceRedisWatcher lettuceRedisWatcherCluster = new LettuceRedisWatcher("192.168.3.244", 6378, redisTopic, "cluster");
+ LettuceRedisWatcher lettuceRedisWatcherCluster = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "cluster");
Assert.assertNotNull(lettuceRedisWatcherCluster);
}
}
\ No newline at end of file
From 0d0a7641bfd603685c416cd86b45521cb84cb3a6 Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Tue, 8 Aug 2023 08:05:32 +0800
Subject: [PATCH 3/7] Update LettuceRedisWatcherTest.java to simplify watcher
initialization and update logic.
---
.../java/org/casbin/test/LettuceRedisWatcherTest.java | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
index cf56ff2..bc149bd 100644
--- a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
+++ b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
@@ -25,19 +25,15 @@ public void initWatcher() {
@Test
public void testUpdate() throws InterruptedException {
- this.initWatcher();
this.lettuceRedisWatcher.update();
Thread.sleep(100);
}
@Test
public void testConsumerCallback() throws InterruptedException {
- this.initWatcher();
- while (true) {
- this.lettuceRedisWatcher.setUpdateCallback((s) -> System.out.println(s));
- this.lettuceRedisWatcher.update();
- Thread.sleep(500);
- }
+ this.lettuceRedisWatcher.setUpdateCallback((s) -> System.out.println(s));
+ this.lettuceRedisWatcher.update();
+ Thread.sleep(500);
}
@Test
From 3adb689bd905c205bd0c92d27b3205490b7d42c2 Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Tue, 8 Aug 2023 13:36:46 +0800
Subject: [PATCH 4/7] Refactor Redis connection initialization and add support
for Redis Cluster.
- Add Redis URI constants.
- Update Redis connection initialization in LettuceRedisWatcher.
- Add constructors for different Redis connection types.
- Add support for Redis Cluster in LettuceRedisWatcher.
- Update LettuceRedisWatcherTest to test Redis Cluster connection.
---
.../watcher/lettuce/LettuceRedisWatcher.java | 86 +++++++++++++------
.../lettuce/constants/WatcherConstant.java | 6 ++
.../casbin/test/LettuceRedisWatcherTest.java | 29 ++++---
3 files changed, 82 insertions(+), 39 deletions(-)
diff --git a/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java b/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java
index 6c51ad0..2776049 100644
--- a/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java
+++ b/src/main/java/org/casbin/watcher/lettuce/LettuceRedisWatcher.java
@@ -1,24 +1,28 @@
package org.casbin.watcher.lettuce;
-import io.lettuce.core.AbstractRedisClient;
-import io.lettuce.core.ClientOptions;
-import io.lettuce.core.RedisClient;
-import io.lettuce.core.RedisURI;
+import io.lettuce.core.*;
import io.lettuce.core.cluster.ClusterClientOptions;
+import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.RedisClusterClient;
+import io.lettuce.core.cluster.RedisClusterURIUtil;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.lang3.StringUtils;
import org.casbin.jcasbin.persist.Watcher;
import org.casbin.watcher.lettuce.constants.WatcherConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.net.URI;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
+import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class LettuceRedisWatcher implements Watcher {
+ private static final Logger logger = LoggerFactory.getLogger(LettuceRedisWatcher.class);
private final String localId;
private final String redisChannelName;
private final AbstractRedisClient abstractRedisClient;
@@ -33,10 +37,9 @@ public class LettuceRedisWatcher implements Watcher {
* @param redisChannelName Redis Channel
* @param timeout Redis Timeout
* @param password Redis Password
- * @param type Redis Type (standalone | cluster)
*/
- public LettuceRedisWatcher(String redisIp, int redisPort, String redisChannelName, int timeout, String password, String type) {
- this.abstractRedisClient = this.getLettuceRedisClient(redisIp, redisPort, password, timeout, type);
+ public LettuceRedisWatcher(String redisIp, Integer redisPort, String redisChannelName, int timeout, String password) {
+ this.abstractRedisClient = this.getLettuceRedisClient(redisIp, redisPort, null, password, timeout, WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE);
this.localId = UUID.randomUUID().toString();
this.redisChannelName = redisChannelName;
this.startSub();
@@ -48,10 +51,24 @@ public LettuceRedisWatcher(String redisIp, int redisPort, String redisChannelNam
* @param redisIp Redis IP
* @param redisPort Redis Port
* @param redisChannelName Redis Channel
- * @param type Redis Type (standalone | cluster)
*/
- public LettuceRedisWatcher(String redisIp, int redisPort, String redisChannelName, String type) {
- this(redisIp, redisPort, redisChannelName, 2000, null, type);
+ public LettuceRedisWatcher(String redisIp, Integer redisPort, String redisChannelName) {
+ this(redisIp, redisPort, redisChannelName, 2000, null);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param nodes Redis Nodes
+ * @param redisChannelName Redis Channel
+ * @param timeout Redis Timeout
+ * @param password Redis Password
+ */
+ public LettuceRedisWatcher(String nodes, String redisChannelName, Integer timeout, String password) {
+ this.abstractRedisClient = this.getLettuceRedisClient(null, null, nodes, password, timeout, WatcherConstant.LETTUCE_REDIS_TYPE_CLUSTER);
+ this.localId = UUID.randomUUID().toString();
+ this.redisChannelName = redisChannelName;
+ this.startSub();
}
@Override
@@ -86,37 +103,38 @@ private void startSub() {
*
* @param host Redis Host
* @param port Redis Port
+ * @param nodes Redis Nodes
* @param password Redis Password
* @param timeout Redis Timeout
* @param type Redis Type (standalone | cluster) default:standalone
* @return AbstractRedisClient
*/
- private AbstractRedisClient getLettuceRedisClient(String host, int port, String password, int timeout, String type) {
+ private AbstractRedisClient getLettuceRedisClient(String host, Integer port, String nodes, String password, int timeout, String type) {
// todo default standalone ?
// type = StringUtils.isEmpty(type) ? WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE : type;
if (StringUtils.isNotEmpty(type) && StringUtils.equalsAnyIgnoreCase(type,
WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE, WatcherConstant.LETTUCE_REDIS_TYPE_CLUSTER)) {
- RedisURI redisUri = null;
- if (StringUtils.isNotEmpty(password)) {
- redisUri = RedisURI.builder()
- .withHost(host)
- .withPort(port)
- .withPassword(password.toCharArray())
- .withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
- .build();
- } else {
- redisUri = RedisURI.builder()
- .withHost(host)
- .withPort(port)
- .withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
- .build();
- }
ClientResources clientResources = DefaultClientResources.builder()
.ioThreadPoolSize(4)
.computationThreadPoolSize(4)
.build();
if (StringUtils.equalsIgnoreCase(type, WatcherConstant.LETTUCE_REDIS_TYPE_STANDALONE)) {
// standalone
+ RedisURI redisUri = null;
+ if (StringUtils.isNotEmpty(password)) {
+ redisUri = RedisURI.builder()
+ .withHost(host)
+ .withPort(port)
+ .withPassword(password.toCharArray())
+ .withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
+ .build();
+ } else {
+ redisUri = RedisURI.builder()
+ .withHost(host)
+ .withPort(port)
+ .withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
+ .build();
+ }
ClientOptions clientOptions = ClientOptions.builder()
.autoReconnect(true)
.pingBeforeActivateConnection(true)
@@ -126,12 +144,26 @@ private AbstractRedisClient getLettuceRedisClient(String host, int port, String
return redisClient;
} else {
// cluster
+ TimeoutOptions timeoutOptions = TimeoutOptions.builder().fixedTimeout(Duration.of(timeout, ChronoUnit.SECONDS)).build();
+ ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
+ .enablePeriodicRefresh(Duration.of(10, ChronoUnit.MINUTES))
+ .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)
+ .adaptiveRefreshTriggersTimeout(Duration.of(30, ChronoUnit.SECONDS))
+ .build();
ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
.autoReconnect(true)
+ .timeoutOptions(timeoutOptions)
+ .topologyRefreshOptions(topologyRefreshOptions)
.pingBeforeActivateConnection(true)
.validateClusterNodeMembership(true)
.build();
- RedisClusterClient redisClusterClient = RedisClusterClient.create(clientResources, redisUri);
+ // Redis Cluster Node
+ String redisUri = StringUtils.isNotEmpty(password) ?
+ WatcherConstant.REDIS_URI_PREFIX.concat(password).concat(WatcherConstant.REDIS_URI_PASSWORD_SPLIT).concat(nodes) :
+ WatcherConstant.REDIS_URI_PREFIX.concat(nodes);
+ logger.info("Redis Cluster Uri: {}", redisUri);
+ List redisURIList = RedisClusterURIUtil.toRedisURIs(URI.create(redisUri));
+ RedisClusterClient redisClusterClient = RedisClusterClient.create(clientResources, redisURIList);
redisClusterClient.setOptions(clusterClientOptions);
return redisClusterClient;
}
diff --git a/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java b/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java
index 0875c08..d0568b1 100644
--- a/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java
+++ b/src/main/java/org/casbin/watcher/lettuce/constants/WatcherConstant.java
@@ -16,4 +16,10 @@ public class WatcherConstant {
*/
public static final String LETTUCE_REDIS_TYPE_STANDALONE = "standalone";
public static final String LETTUCE_REDIS_TYPE_CLUSTER = "cluster";
+
+ /**
+ * Redis URI
+ */
+ public static final String REDIS_URI_PREFIX = "redis://";
+ public static final String REDIS_URI_PASSWORD_SPLIT = "@";
}
\ No newline at end of file
diff --git a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
index bc149bd..601d209 100644
--- a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
+++ b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
@@ -18,42 +18,47 @@ public class LettuceRedisWatcherTest {
@Before
public void initWatcher() {
String redisTopic = "jcasbin-topic";
- this.lettuceRedisWatcher = new LettuceRedisWatcher("127.0.0.1", 6379, redisTopic, 2000, "foobared", "standalone");
+ this.lettuceRedisWatcher = new LettuceRedisWatcher("192.168.3.244", 6379, redisTopic, 2000, "123456");
+ Enforcer enforcer = new Enforcer();
+ enforcer.setWatcher(this.lettuceRedisWatcher);
+ }
+
+ public void initClusterWatcher() {
+ String redisTopic = "jcasbin-topic";
+ // modify your cluster nodes
+ this.lettuceRedisWatcher = new LettuceRedisWatcher("192.168.1.234:6380,192.168.1.234:6381,192.168.1.234:6382", redisTopic, 2000, "123456");
Enforcer enforcer = new Enforcer();
enforcer.setWatcher(this.lettuceRedisWatcher);
}
@Test
public void testUpdate() throws InterruptedException {
+ // this.initClusterWatcher();
this.lettuceRedisWatcher.update();
Thread.sleep(100);
}
@Test
public void testConsumerCallback() throws InterruptedException {
+ // this.initClusterWatcher();
+ // while (true) {
this.lettuceRedisWatcher.setUpdateCallback((s) -> System.out.println(s));
this.lettuceRedisWatcher.update();
- Thread.sleep(500);
+ Thread.sleep(100);
+ // }
}
@Test
public void testConnectWatcherWithoutPassword() {
String redisTopic = "jcasbin-topic";
- LettuceRedisWatcher lettuceRedisWatcherWithoutPassword = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "standalone");
+ LettuceRedisWatcher lettuceRedisWatcherWithoutPassword = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic);
Assert.assertNotNull(lettuceRedisWatcherWithoutPassword);
}
@Test
- public void testConnectWatcherWithType() {
+ public void testConnectWatcherCluster() {
String redisTopic = "jcasbin-topic";
- Assert.assertThrows(IllegalArgumentException.class, () -> {
- new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "sentinel");
- });
-
- LettuceRedisWatcher lettuceRedisWatcherStandalone = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "standalone");
- Assert.assertNotNull(lettuceRedisWatcherStandalone);
-
- LettuceRedisWatcher lettuceRedisWatcherCluster = new LettuceRedisWatcher("127.0.0.1", 6378, redisTopic, "cluster");
+ LettuceRedisWatcher lettuceRedisWatcherCluster = new LettuceRedisWatcher("127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382", redisTopic, 2000, null);
Assert.assertNotNull(lettuceRedisWatcherCluster);
}
}
\ No newline at end of file
From 28b8232606cfd3517df9d6bc414063bce67852ea Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Tue, 8 Aug 2023 13:39:04 +0800
Subject: [PATCH 5/7] Update LettuceRedisWatcherTest.java to use localhost as
the redis server instead of the specific IP address.
---
src/test/java/org/casbin/test/LettuceRedisWatcherTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
index 601d209..71ee15d 100644
--- a/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
+++ b/src/test/java/org/casbin/test/LettuceRedisWatcherTest.java
@@ -18,7 +18,7 @@ public class LettuceRedisWatcherTest {
@Before
public void initWatcher() {
String redisTopic = "jcasbin-topic";
- this.lettuceRedisWatcher = new LettuceRedisWatcher("192.168.3.244", 6379, redisTopic, 2000, "123456");
+ this.lettuceRedisWatcher = new LettuceRedisWatcher("127.0.0.1", 6379, redisTopic, 2000, "foobared");
Enforcer enforcer = new Enforcer();
enforcer.setWatcher(this.lettuceRedisWatcher);
}
From a1f16af464d80f45d17d58c0a52afd70735ba8c7 Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Tue, 8 Aug 2023 14:28:39 +0800
Subject: [PATCH 6/7] Update node version from 16 to 18 in maven-ci.yml and add
new semantic.yml.
---
.github/semantic.yml | 5 +++++
.github/workflows/maven-ci.yml | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 .github/semantic.yml
diff --git a/.github/semantic.yml b/.github/semantic.yml
new file mode 100644
index 0000000..775c00d
--- /dev/null
+++ b/.github/semantic.yml
@@ -0,0 +1,5 @@
+type:
+ - feat
+
+scope:
+ - add lettuce
\ No newline at end of file
diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml
index 12d6f9f..b0d1e8f 100644
--- a/.github/workflows/maven-ci.yml
+++ b/.github/workflows/maven-ci.yml
@@ -45,7 +45,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v2
with:
- node-version: 16
+ node-version: 18
- name: Sematic Release
run: |
From 01ea04d04bae136ea4c5c6ddb9dbfa4937390c32 Mon Sep 17 00:00:00 2001
From: shingmoyeung <525032303@qq.com>
Date: Tue, 8 Aug 2023 14:35:19 +0800
Subject: [PATCH 7/7] Refactor semantic.yml to disable validation for the PR
title and all the commits.
---
.github/semantic.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/.github/semantic.yml b/.github/semantic.yml
index 775c00d..e500cff 100644
--- a/.github/semantic.yml
+++ b/.github/semantic.yml
@@ -1,5 +1,2 @@
-type:
- - feat
-
-scope:
- - add lettuce
\ No newline at end of file
+# Always validate the PR title AND all the commits
+titleAndCommits: false
\ No newline at end of file