This is an automated email from the ASF dual-hosted git repository. duanzhengqiang pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/shardingsphere-plugin.git
commit 9e37db21b1fdcab735baa7533f6d3ae510cd42b6 Author: duanzhengqiang <[email protected]> AuthorDate: Fri Sep 8 18:57:06 2023 +0800 Move sharding and encrypt plugin to shardingsphere plugin --- feature-plugin/encrypt-plugin/pom.xml | 33 +++++ feature-plugin/encrypt-plugin/sm/pom.xml | 61 ++++++++ .../encrypt/sm/algorithm/SM3EncryptAlgorithm.java | 93 ++++++++++++ .../encrypt/sm/algorithm/SM4EncryptAlgorithm.java | 157 ++++++++++++++++++++ ...che.shardingsphere.encrypt.spi.EncryptAlgorithm | 19 +++ .../sm/algorithm/SM3EncryptAlgorithmTest.java | 80 +++++++++++ .../sm/algorithm/SM4EncryptAlgorithmTest.java | 96 +++++++++++++ feature-plugin/pom.xml | 34 +++++ feature-plugin/sharding-plugin/cosid/pom.xml | 53 +++++++ .../cosid/algorithm/CosIdAlgorithmConstants.java | 34 +++++ .../keygen/CosIdKeyGenerateAlgorithm.java | 58 ++++++++ .../keygen/CosIdSnowflakeKeyGenerateAlgorithm.java | 103 +++++++++++++ .../AbstractCosIdIntervalShardingAlgorithm.java | 117 +++++++++++++++ .../interval/CosIdIntervalShardingAlgorithm.java | 53 +++++++ .../CosIdSnowflakeIntervalShardingAlgorithm.java | 56 ++++++++ .../sharding/mod/CosIdModShardingAlgorithm.java | 69 +++++++++ ...hardingsphere.sharding.spi.KeyGenerateAlgorithm | 19 +++ ...e.shardingsphere.sharding.spi.ShardingAlgorithm | 20 +++ .../keygen/CosIdKeyGenerateAlgorithmTest.java | 85 +++++++++++ .../CosIdSnowflakeKeyGenerateAlgorithmTest.java | 160 +++++++++++++++++++++ .../keygen/fixture/WorkerIdGeneratorFixture.java | 34 +++++ feature-plugin/sharding-plugin/nanoid/pom.xml | 46 ++++++ .../keygen/NanoIdKeyGenerateAlgorithm.java | 39 +++++ ...hardingsphere.sharding.spi.KeyGenerateAlgorithm | 18 +++ .../keygen/NanoIdKeyGenerateAlgorithmTest.java | 33 +++++ feature-plugin/sharding-plugin/pom.xml | 34 +++++ pom.xml | 66 +++++++++ 27 files changed, 1670 insertions(+) diff --git a/feature-plugin/encrypt-plugin/pom.xml b/feature-plugin/encrypt-plugin/pom.xml new file mode 100644 index 0000000..ea5270e --- /dev/null +++ b/feature-plugin/encrypt-plugin/pom.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-feature-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + </parent> + <artifactId>shardingsphere-feature-encrypt-plugin</artifactId> + <packaging>pom</packaging> + <name>${project.artifactId}</name> + + <modules> + <module>sm</module> + </modules> +</project> diff --git a/feature-plugin/encrypt-plugin/sm/pom.xml b/feature-plugin/encrypt-plugin/sm/pom.xml new file mode 100644 index 0000000..b9f2004 --- /dev/null +++ b/feature-plugin/encrypt-plugin/sm/pom.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-encrypt-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + </parent> + <artifactId>shardingsphere-encrypt-plugin-sm</artifactId> + <name>${project.artifactId}</name> + + <dependencies> + <dependency> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-encrypt-api</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-encrypt-core</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-test-util</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>${bouncycastle.version}</version> + </dependency> + + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + <version>${commons-codec.version}</version> + </dependency> + </dependencies> +</project> diff --git a/feature-plugin/encrypt-plugin/sm/src/main/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM3EncryptAlgorithm.java b/feature-plugin/encrypt-plugin/sm/src/main/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM3EncryptAlgorithm.java new file mode 100644 index 0000000..33317c0 --- /dev/null +++ b/feature-plugin/encrypt-plugin/sm/src/main/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM3EncryptAlgorithm.java @@ -0,0 +1,93 @@ +/* + * 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.shardingsphere.encrypt.sm.algorithm; + +import lombok.EqualsAndHashCode; +import org.apache.commons.codec.binary.Hex; +import org.apache.shardingsphere.encrypt.api.encrypt.standard.StandardEncryptAlgorithm; +import org.apache.shardingsphere.encrypt.exception.algorithm.EncryptAlgorithmInitializationException; +import org.apache.shardingsphere.encrypt.api.context.EncryptContext; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.nio.charset.StandardCharsets; +import java.security.Security; +import java.util.Properties; + +/** + * SM3 encrypt algorithm. + */ +@EqualsAndHashCode +public final class SM3EncryptAlgorithm implements StandardEncryptAlgorithm<Object, String> { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private static final String SM3_SALT = "sm3-salt"; + + private static final int SALT_LENGTH = 8; + + private byte[] sm3Salt; + + @Override + public void init(final Properties props) { + sm3Salt = createSm3Salt(props); + } + + private byte[] createSm3Salt(final Properties props) { + String salt = null == props.getProperty(SM3_SALT) ? "" : String.valueOf(props.getProperty(SM3_SALT)); + ShardingSpherePreconditions.checkState(salt.isEmpty() || SALT_LENGTH == salt.length(), + () -> new EncryptAlgorithmInitializationException("SM3", "Salt should be either blank or better " + SALT_LENGTH + " bytes long.")); + return salt.isEmpty() ? new byte[0] : salt.getBytes(StandardCharsets.UTF_8); + } + + @Override + public String encrypt(final Object plainValue, final EncryptContext encryptContext) { + return null == plainValue ? null : Hex.encodeHexString(digest(String.valueOf(plainValue).getBytes(StandardCharsets.UTF_8), sm3Salt)); + } + + @Override + public Object decrypt(final String cipherValue, final EncryptContext encryptContext) { + return cipherValue; + } + + private byte[] digest(final byte[] input, final byte[] salt) { + SM3Digest sm3Digest = new SM3Digest(); + byte[] updateByte = concat(input, salt); + sm3Digest.update(updateByte, 0, updateByte.length); + byte[] result = new byte[sm3Digest.getDigestSize()]; + sm3Digest.doFinal(result, 0); + return result; + } + + private byte[] concat(final byte[] input, final byte[] salt) { + int inputLength = input.length; + int saltLength = salt.length; + byte[] result = new byte[inputLength + saltLength]; + System.arraycopy(input, 0, result, 0, inputLength); + System.arraycopy(salt, 0, result, inputLength, saltLength); + return result; + } + + @Override + public String getType() { + return "SM3"; + } +} diff --git a/feature-plugin/encrypt-plugin/sm/src/main/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM4EncryptAlgorithm.java b/feature-plugin/encrypt-plugin/sm/src/main/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM4EncryptAlgorithm.java new file mode 100644 index 0000000..61035f1 --- /dev/null +++ b/feature-plugin/encrypt-plugin/sm/src/main/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM4EncryptAlgorithm.java @@ -0,0 +1,157 @@ +/* + * 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.shardingsphere.encrypt.sm.algorithm; + +import lombok.EqualsAndHashCode; +import lombok.SneakyThrows; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.shardingsphere.encrypt.api.encrypt.standard.StandardEncryptAlgorithm; +import org.apache.shardingsphere.encrypt.exception.algorithm.EncryptAlgorithmInitializationException; +import org.apache.shardingsphere.encrypt.api.context.EncryptContext; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.Security; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +/** + * SM4 encrypt algorithm. + */ +@EqualsAndHashCode +public final class SM4EncryptAlgorithm implements StandardEncryptAlgorithm<Object, String> { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private static final String SM4_KEY = "sm4-key"; + + private static final String SM4_IV = "sm4-iv"; + + private static final String SM4_MODE = "sm4-mode"; + + private static final String SM4_PADDING = "sm4-padding"; + + private static final int KEY_LENGTH = 16; + + private static final int IV_LENGTH = 16; + + private static final Set<String> MODES = new HashSet<>(Arrays.asList("ECB", "CBC")); + + private static final Set<String> PADDINGS = new HashSet<>(Arrays.asList("PKCS5Padding", "PKCS7Padding")); + + private byte[] sm4Key; + + private byte[] sm4Iv; + + private String sm4ModePadding; + + @Override + public void init(final Properties props) { + String sm4Mode = createSm4Mode(props); + String sm4Padding = createSm4Padding(props); + sm4ModePadding = "SM4/" + sm4Mode + "/" + sm4Padding; + sm4Key = createSm4Key(props); + sm4Iv = createSm4Iv(props, sm4Mode); + } + + private String createSm4Mode(final Properties props) { + ShardingSpherePreconditions.checkState(props.containsKey(SM4_MODE), () -> new EncryptAlgorithmInitializationException("SM4", String.format("%s can not be null", SM4_MODE))); + String result = String.valueOf(props.getProperty(SM4_MODE)).toUpperCase(); + ShardingSpherePreconditions.checkState(MODES.contains(result), () -> new EncryptAlgorithmInitializationException("SM4", "Mode must be either CBC or ECB")); + return result; + } + + private byte[] createSm4Key(final Properties props) { + ShardingSpherePreconditions.checkState(props.containsKey(SM4_KEY), () -> new EncryptAlgorithmInitializationException("SM4", String.format("%s can not be null", SM4_KEY))); + byte[] result = fromHexString(String.valueOf(props.getProperty(SM4_KEY))); + ShardingSpherePreconditions.checkState(KEY_LENGTH == result.length, + () -> new EncryptAlgorithmInitializationException("SM4", "Key length must be " + KEY_LENGTH + " bytes long")); + return result; + } + + private byte[] createSm4Iv(final Properties props, final String sm4Mode) { + if (!"CBC".equalsIgnoreCase(sm4Mode)) { + return new byte[0]; + } + ShardingSpherePreconditions.checkState(props.containsKey(SM4_IV), () -> new EncryptAlgorithmInitializationException("SM4", String.format("%s can not be null", SM4_IV))); + String sm4IvValue = String.valueOf(props.getProperty(SM4_IV)); + byte[] result = fromHexString(sm4IvValue); + ShardingSpherePreconditions.checkState(IV_LENGTH == result.length, () -> new EncryptAlgorithmInitializationException("SM4", "Iv length must be " + IV_LENGTH + " bytes long")); + return result; + } + + private String createSm4Padding(final Properties props) { + ShardingSpherePreconditions.checkState(props.containsKey(SM4_PADDING), () -> new EncryptAlgorithmInitializationException("SM4", String.format("%s can not be null", SM4_PADDING))); + String result = String.valueOf(props.getProperty(SM4_PADDING)).toUpperCase().replace("PADDING", "Padding"); + ShardingSpherePreconditions.checkState(PADDINGS.contains(result), () -> new EncryptAlgorithmInitializationException("SM4", "Padding must be either PKCS5Padding or PKCS7Padding")); + return result; + } + + @Override + public String encrypt(final Object plainValue, final EncryptContext encryptContext) { + return null == plainValue ? null : Hex.encodeHexString(encrypt(String.valueOf(plainValue).getBytes(StandardCharsets.UTF_8))); + } + + private byte[] encrypt(final byte[] plainValue) { + return handle(plainValue, Cipher.ENCRYPT_MODE); + } + + @Override + public Object decrypt(final String cipherValue, final EncryptContext encryptContext) { + return null == cipherValue ? null : new String(decrypt(fromHexString(cipherValue)), StandardCharsets.UTF_8); + } + + private byte[] decrypt(final byte[] cipherValue) { + return handle(cipherValue, Cipher.DECRYPT_MODE); + } + + @SneakyThrows(GeneralSecurityException.class) + private byte[] handle(final byte[] input, final int mode) { + Cipher cipher = Cipher.getInstance(sm4ModePadding, BouncyCastleProvider.PROVIDER_NAME); + SecretKeySpec secretKeySpec = new SecretKeySpec(sm4Key, "SM4"); + if (0 == sm4Iv.length) { + cipher.init(mode, secretKeySpec); + } else { + cipher.init(mode, secretKeySpec, new IvParameterSpec(sm4Iv)); + } + return cipher.doFinal(input); + } + + @Override + public String getType() { + return "SM4"; + } + + static byte[] fromHexString(final String s) { + try { + return Hex.decodeHex(s); + } catch (final DecoderException ex) { + throw new EncryptAlgorithmInitializationException("SM", ex.getMessage()); + } + } +} diff --git a/feature-plugin/encrypt-plugin/sm/src/main/resources/META-INF/services/org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm b/feature-plugin/encrypt-plugin/sm/src/main/resources/META-INF/services/org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm new file mode 100644 index 0000000..630f752 --- /dev/null +++ b/feature-plugin/encrypt-plugin/sm/src/main/resources/META-INF/services/org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm @@ -0,0 +1,19 @@ +# +# 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. +# + +org.apache.shardingsphere.encrypt.sm.algorithm.SM3EncryptAlgorithm +org.apache.shardingsphere.encrypt.sm.algorithm.SM4EncryptAlgorithm diff --git a/feature-plugin/encrypt-plugin/sm/src/test/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM3EncryptAlgorithmTest.java b/feature-plugin/encrypt-plugin/sm/src/test/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM3EncryptAlgorithmTest.java new file mode 100644 index 0000000..56a38c6 --- /dev/null +++ b/feature-plugin/encrypt-plugin/sm/src/test/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM3EncryptAlgorithmTest.java @@ -0,0 +1,80 @@ +/* + * 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.shardingsphere.encrypt.sm.algorithm; + +import org.apache.shardingsphere.encrypt.api.encrypt.standard.StandardEncryptAlgorithm; +import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm; +import org.apache.shardingsphere.encrypt.api.context.EncryptContext; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.test.util.PropertiesBuilder; +import org.apache.shardingsphere.test.util.PropertiesBuilder.Property; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; + +class SM3EncryptAlgorithmTest { + + private StandardEncryptAlgorithm<Object, String> encryptAlgorithm; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + encryptAlgorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM3", PropertiesBuilder.build(new Property("sm3-salt", "test1234"))); + } + + @Test + void assertEncrypt() { + Object actual = encryptAlgorithm.encrypt("test1234", mock(EncryptContext.class)); + assertThat(actual, is("9587fe084ee4b53fe629c6ae5519ee4d55def8ed4badc8588d3be9b99bd84aba")); + } + + @Test + void assertEncryptWithoutSalt() { + encryptAlgorithm.init(new Properties()); + assertThat(encryptAlgorithm.encrypt("test1234", mock(EncryptContext.class)), is("ab847c6f2f6a53be88808c5221bd6ee0762e1af1def82b21d2061599b6cf5c79")); + } + + @Test + void assertEncryptWithNullPlaintext() { + assertNull(encryptAlgorithm.encrypt(null, mock(EncryptContext.class))); + } + + @Test + void assertDecrypt() { + Object actual = encryptAlgorithm.decrypt("ab847c6f2f6a53be88808c5221bd6ee0762e1af1def82b21d2061599b6cf5c79", mock(EncryptContext.class)); + assertThat(actual.toString(), is("ab847c6f2f6a53be88808c5221bd6ee0762e1af1def82b21d2061599b6cf5c79")); + } + + @Test + void assertDecryptWithoutSalt() { + encryptAlgorithm.init(new Properties()); + Object actual = encryptAlgorithm.decrypt("ab847c6f2f6a53be88808c5221bd6ee0762e1af1def82b21d2061599b6cf5c79", mock(EncryptContext.class)); + assertThat(actual.toString(), is("ab847c6f2f6a53be88808c5221bd6ee0762e1af1def82b21d2061599b6cf5c79")); + } + + @Test + void assertDecryptWithNullCiphertext() { + assertNull(encryptAlgorithm.decrypt(null, mock(EncryptContext.class))); + } +} diff --git a/feature-plugin/encrypt-plugin/sm/src/test/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM4EncryptAlgorithmTest.java b/feature-plugin/encrypt-plugin/sm/src/test/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM4EncryptAlgorithmTest.java new file mode 100644 index 0000000..a2de944 --- /dev/null +++ b/feature-plugin/encrypt-plugin/sm/src/test/java/org/apache/shardingsphere/encrypt/sm/algorithm/SM4EncryptAlgorithmTest.java @@ -0,0 +1,96 @@ +/* + * 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.shardingsphere.encrypt.sm.algorithm; + +import org.apache.shardingsphere.encrypt.api.encrypt.standard.StandardEncryptAlgorithm; +import org.apache.shardingsphere.encrypt.exception.algorithm.EncryptAlgorithmInitializationException; +import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm; +import org.apache.shardingsphere.encrypt.api.context.EncryptContext; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.test.util.PropertiesBuilder; +import org.apache.shardingsphere.test.util.PropertiesBuilder.Property; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +class SM4EncryptAlgorithmTest { + + @Test + void assertInitWithoutKey() { + assertThrows(EncryptAlgorithmInitializationException.class, + () -> TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", PropertiesBuilder.build(new Property("sm4-mode", "ECB"), new Property("sm4-padding", "PKCS5Padding")))); + } + + @SuppressWarnings("unchecked") + @Test + void assertEncryptNullValue() { + StandardEncryptAlgorithm<Object, String> algorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", createECBProperties()); + assertNull(algorithm.encrypt(null, mock(EncryptContext.class))); + } + + @SuppressWarnings("unchecked") + @Test + void assertEncryptWithECBMode() { + StandardEncryptAlgorithm<Object, String> algorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", createECBProperties()); + assertThat(algorithm.encrypt("test", mock(EncryptContext.class)), is("028654f2ca4f575dee9e1faae85dadde")); + } + + @SuppressWarnings("unchecked") + @Test + void assertDecryptNullValue() { + StandardEncryptAlgorithm<Object, String> algorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", createECBProperties()); + assertNull(algorithm.decrypt(null, mock(EncryptContext.class))); + } + + @SuppressWarnings("unchecked") + @Test + void assertDecryptWithECBMode() { + StandardEncryptAlgorithm<Object, String> algorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", createECBProperties()); + assertThat(algorithm.decrypt("028654f2ca4f575dee9e1faae85dadde", mock(EncryptContext.class)).toString(), is("test")); + } + + private Properties createECBProperties() { + return PropertiesBuilder.build(new Property("sm4-key", "4D744E003D713D054E7E407C350E447E"), new Property("sm4-mode", "ECB"), new Property("sm4-padding", "PKCS5Padding")); + } + + @SuppressWarnings("unchecked") + @Test + void assertEncryptWithCBCMode() { + StandardEncryptAlgorithm<Object, String> algorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", createCBCProperties()); + assertThat(algorithm.encrypt("test", mock(EncryptContext.class)), is("dca2127b57ba8cac36a0914e0208dc11")); + } + + @SuppressWarnings("unchecked") + @Test + void assertDecrypt() { + StandardEncryptAlgorithm<Object, String> algorithm = (StandardEncryptAlgorithm<Object, String>) TypedSPILoader.getService(EncryptAlgorithm.class, "SM4", createCBCProperties()); + assertThat(algorithm.decrypt("dca2127b57ba8cac36a0914e0208dc11", mock(EncryptContext.class)).toString(), is("test")); + } + + private Properties createCBCProperties() { + return PropertiesBuilder.build( + new Property("sm4-key", "f201326119911788cFd30575b81059ac"), new Property("sm4-iv", "e166c3391294E69cc4c620f594fe00d7"), + new Property("sm4-mode", "CBC"), new Property("sm4-padding", "PKCS7Padding")); + } +} diff --git a/feature-plugin/pom.xml b/feature-plugin/pom.xml new file mode 100644 index 0000000..246f5c4 --- /dev/null +++ b/feature-plugin/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + </parent> + <artifactId>shardingsphere-feature-plugin</artifactId> + <packaging>pom</packaging> + <name>${project.artifactId}</name> + + <modules> + <module>sharding-plugin</module> + <module>encrypt-plugin</module> + </modules> +</project> diff --git a/feature-plugin/sharding-plugin/cosid/pom.xml b/feature-plugin/sharding-plugin/cosid/pom.xml new file mode 100644 index 0000000..d3932f7 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/pom.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-sharding-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + </parent> + <artifactId>shardingsphere-sharding-plugin-cosid</artifactId> + <name>${project.artifactId}</name> + + <properties> + <cosid.version>1.18.5</cosid.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-sharding-api</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-test-util</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>me.ahoo.cosid</groupId> + <artifactId>cosid-core</artifactId> + <version>${cosid.version}</version> + </dependency> + </dependencies> +</project> diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/CosIdAlgorithmConstants.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/CosIdAlgorithmConstants.java new file mode 100644 index 0000000..4d8921f --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/CosIdAlgorithmConstants.java @@ -0,0 +1,34 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * CosId algorithm constants. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class CosIdAlgorithmConstants { + + public static final String TYPE_PREFIX = "COSID_"; + + public static final String ID_NAME_KEY = "id-name"; + + public static final String LOGIC_NAME_PREFIX_KEY = "logic-name-prefix"; +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdKeyGenerateAlgorithm.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdKeyGenerateAlgorithm.java new file mode 100644 index 0000000..3c7b59a --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdKeyGenerateAlgorithm.java @@ -0,0 +1,58 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.keygen; + +import me.ahoo.cosid.CosId; +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.provider.LazyIdGenerator; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; +import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm; + +import java.util.Properties; + +/** + * CosId key generate algorithm. + */ +public final class CosIdKeyGenerateAlgorithm implements KeyGenerateAlgorithm { + + private static final String AS_STRING_KEY = "as-string"; + + private LazyIdGenerator lazyIdGenerator; + + private boolean asString; + + @Override + public void init(final Properties props) { + lazyIdGenerator = new LazyIdGenerator(props.getProperty(CosIdAlgorithmConstants.ID_NAME_KEY, IdGeneratorProvider.SHARE)); + asString = Boolean.parseBoolean(props.getProperty(AS_STRING_KEY, Boolean.FALSE.toString())); + lazyIdGenerator.tryGet(false); + } + + @Override + public Comparable<?> generateKey() { + if (asString) { + return lazyIdGenerator.generateAsString(); + } + return lazyIdGenerator.generate(); + } + + @Override + public String getType() { + return CosId.COSID.toUpperCase(); + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdSnowflakeKeyGenerateAlgorithm.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdSnowflakeKeyGenerateAlgorithm.java new file mode 100644 index 0000000..fe437e8 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdSnowflakeKeyGenerateAlgorithm.java @@ -0,0 +1,103 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.keygen; + +import me.ahoo.cosid.converter.Radix62IdConverter; +import me.ahoo.cosid.snowflake.ClockSyncSnowflakeId; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.SnowflakeId; +import me.ahoo.cosid.snowflake.StringSnowflakeId; +import org.apache.shardingsphere.infra.instance.InstanceContext; +import org.apache.shardingsphere.infra.instance.InstanceContextAware; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; +import org.apache.shardingsphere.sharding.exception.ShardingPluginException; +import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Properties; + +/** + * CosId snowflake key generate algorithm. + */ +public final class CosIdSnowflakeKeyGenerateAlgorithm implements KeyGenerateAlgorithm, InstanceContextAware { + + public static final long DEFAULT_EPOCH; + + public static final String AS_STRING_KEY = "as-string"; + + public static final String EPOCH_KEY = "epoch"; + + private Properties props; + + private SnowflakeId snowflakeId; + + private boolean asString; + + private long epoch; + + static { + DEFAULT_EPOCH = LocalDateTime.of(2016, 11, 1, 0, 0, 0).toInstant(ZoneId.systemDefault().getRules().getOffset(Instant.now())).toEpochMilli(); + } + + @Override + public void init(final Properties props) { + this.props = props; + asString = getAsString(props); + epoch = getEpoch(props); + } + + private boolean getAsString(final Properties props) { + return Boolean.parseBoolean(props.getProperty(AS_STRING_KEY, Boolean.FALSE.toString())); + } + + private long getEpoch(final Properties props) { + long result = Long.parseLong(props.getProperty(EPOCH_KEY, String.valueOf(DEFAULT_EPOCH))); + ShardingSpherePreconditions.checkState(result > 0L, + () -> new ShardingPluginException("Key generate algorithm `%s` initialization failed, reason is: %s.", getType(), "Epoch must be positive.")); + return result; + } + + @Override + public void setInstanceContext(final InstanceContext instanceContext) { + int workerId = instanceContext.generateWorkerId(props); + MillisecondSnowflakeId millisecondSnowflakeId = + new MillisecondSnowflakeId(epoch, MillisecondSnowflakeId.DEFAULT_TIMESTAMP_BIT, MillisecondSnowflakeId.DEFAULT_MACHINE_BIT, MillisecondSnowflakeId.DEFAULT_SEQUENCE_BIT, workerId); + snowflakeId = new StringSnowflakeId(new ClockSyncSnowflakeId(millisecondSnowflakeId), Radix62IdConverter.PAD_START); + } + + @Override + public Comparable<?> generateKey() { + if (asString) { + return getSnowflakeId().generateAsString(); + } + return getSnowflakeId().generate(); + } + + private SnowflakeId getSnowflakeId() { + ShardingSpherePreconditions.checkNotNull(snowflakeId, () -> new ShardingPluginException("Instance context not set yet.")); + return snowflakeId; + } + + @Override + public String getType() { + return CosIdAlgorithmConstants.TYPE_PREFIX + "SNOWFLAKE"; + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/AbstractCosIdIntervalShardingAlgorithm.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/AbstractCosIdIntervalShardingAlgorithm.java new file mode 100644 index 0000000..61f99ce --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/AbstractCosIdIntervalShardingAlgorithm.java @@ -0,0 +1,117 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.sharding.interval; + +import com.google.common.collect.BoundType; +import com.google.common.collect.Range; +import me.ahoo.cosid.sharding.IntervalStep; +import me.ahoo.cosid.sharding.IntervalTimeline; +import me.ahoo.cosid.sharding.LocalDateTimeConvertor; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; +import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; +import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; +import org.apache.shardingsphere.sharding.exception.ShardingPluginException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Properties; + +/** + * Abstract interval range sharding algorithm with CosId. + * + * @param <T> type of sharding value + */ +public abstract class AbstractCosIdIntervalShardingAlgorithm<T extends Comparable<?>> implements StandardShardingAlgorithm<T> { + + public static final String DEFAULT_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + public static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN); + + public static final String DATE_TIME_LOWER_KEY = "datetime-lower"; + + public static final String DATE_TIME_UPPER_KEY = "datetime-upper"; + + public static final String SHARDING_SUFFIX_FORMAT_KEY = "sharding-suffix-pattern"; + + public static final String INTERVAL_UNIT_KEY = "datetime-interval-unit"; + + public static final String INTERVAL_AMOUNT_KEY = "datetime-interval-amount"; + + private IntervalTimeline intervalTimeline; + + private LocalDateTimeConvertor localDateTimeConvertor; + + @Override + public void init(final Properties props) { + intervalTimeline = createIntervalTimeline(props); + localDateTimeConvertor = createLocalDateTimeConvertor(props); + } + + private IntervalTimeline createIntervalTimeline(final Properties props) { + String logicNamePrefix = getRequiredValue(props, CosIdAlgorithmConstants.LOGIC_NAME_PREFIX_KEY); + LocalDateTime effectiveLower = LocalDateTime.parse(getRequiredValue(props, DATE_TIME_LOWER_KEY), DEFAULT_DATE_TIME_FORMATTER); + LocalDateTime effectiveUpper = LocalDateTime.parse(getRequiredValue(props, DATE_TIME_UPPER_KEY), DEFAULT_DATE_TIME_FORMATTER); + ChronoUnit stepUnit = ChronoUnit.valueOf(getRequiredValue(props, INTERVAL_UNIT_KEY)); + int stepAmount = Integer.parseInt(props.getOrDefault(INTERVAL_AMOUNT_KEY, 1).toString()); + DateTimeFormatter suffixFormatter = DateTimeFormatter.ofPattern(getRequiredValue(props, SHARDING_SUFFIX_FORMAT_KEY)); + return new IntervalTimeline(logicNamePrefix, Range.closed(effectiveLower, effectiveUpper), IntervalStep.of(stepUnit, stepAmount), suffixFormatter); + } + + private String getRequiredValue(final Properties props, final String key) { + ShardingSpherePreconditions.checkState(props.containsKey(key), () -> new ShardingPluginException("%s can not be null.", key)); + return props.getProperty(key); + } + + protected abstract LocalDateTimeConvertor createLocalDateTimeConvertor(Properties props); + + @Override + public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<T> shardingValue) { + return intervalTimeline.sharding(localDateTimeConvertor.toLocalDateTime(shardingValue.getValue())); + } + + @Override + public Collection<String> doSharding(final Collection<String> availableTargetNames, final RangeShardingValue<T> shardingValue) { + return intervalTimeline.sharding(toLocalDateTimeRange(shardingValue.getValueRange())); + } + + @SuppressWarnings("unchecked") + private Range<LocalDateTime> toLocalDateTimeRange(final Range<T> shardingValue) { + if (Range.all().equals(shardingValue)) { + return Range.all(); + } + Object endpointValue = shardingValue.hasLowerBound() ? shardingValue.lowerEndpoint() : shardingValue.upperEndpoint(); + if (endpointValue instanceof LocalDateTime) { + return (Range<LocalDateTime>) shardingValue; + } + if (shardingValue.hasLowerBound() && shardingValue.hasUpperBound()) { + LocalDateTime lower = localDateTimeConvertor.toLocalDateTime(shardingValue.lowerEndpoint()); + LocalDateTime upper = localDateTimeConvertor.toLocalDateTime(shardingValue.upperEndpoint()); + return Range.range(lower, shardingValue.lowerBoundType(), upper, shardingValue.upperBoundType()); + } + if (shardingValue.hasLowerBound()) { + LocalDateTime lower = localDateTimeConvertor.toLocalDateTime(shardingValue.lowerEndpoint()); + return BoundType.OPEN == shardingValue.lowerBoundType() ? Range.greaterThan(lower) : Range.atLeast(lower); + } + LocalDateTime upper = localDateTimeConvertor.toLocalDateTime(shardingValue.upperEndpoint()); + return BoundType.OPEN == shardingValue.upperBoundType() ? Range.lessThan(upper) : Range.atMost(upper); + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/CosIdIntervalShardingAlgorithm.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/CosIdIntervalShardingAlgorithm.java new file mode 100644 index 0000000..c79b5a5 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/CosIdIntervalShardingAlgorithm.java @@ -0,0 +1,53 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.sharding.interval; + +import me.ahoo.cosid.sharding.LocalDateTimeConvertor; +import me.ahoo.cosid.sharding.StandardLocalDateTimeConvertor; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Properties; + +/** + * Interval sharding algorithm with CosId. + */ +public final class CosIdIntervalShardingAlgorithm extends AbstractCosIdIntervalShardingAlgorithm<Comparable<?>> { + + private static final String ZONE_ID_KEY = "zone-id"; + + private static final String DATE_TIME_PATTERN_KEY = "datetime-pattern"; + + private static final String TIMESTAMP_SECOND_UNIT = "SECOND"; + + private static final String TIMESTAMP_UNIT_KEY = "ts-unit"; + + @Override + protected LocalDateTimeConvertor createLocalDateTimeConvertor(final Properties props) { + ZoneId zoneId = props.containsKey(ZONE_ID_KEY) ? ZoneId.of(props.getProperty(ZONE_ID_KEY)) : ZoneId.systemDefault(); + boolean isSecondTs = props.containsKey(TIMESTAMP_UNIT_KEY) && TIMESTAMP_SECOND_UNIT.equalsIgnoreCase(props.getProperty(TIMESTAMP_UNIT_KEY)); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(props.getProperty(DATE_TIME_PATTERN_KEY, DEFAULT_DATE_TIME_PATTERN)); + return new StandardLocalDateTimeConvertor(zoneId, isSecondTs, dateTimeFormatter); + } + + @Override + public String getType() { + return CosIdAlgorithmConstants.TYPE_PREFIX + "INTERVAL"; + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/CosIdSnowflakeIntervalShardingAlgorithm.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/CosIdSnowflakeIntervalShardingAlgorithm.java new file mode 100644 index 0000000..966b610 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/interval/CosIdSnowflakeIntervalShardingAlgorithm.java @@ -0,0 +1,56 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.sharding.interval; + +import me.ahoo.cosid.sharding.LocalDateTimeConvertor; +import me.ahoo.cosid.sharding.SnowflakeLocalDateTimeConvertor; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeIdStateParser; +import me.ahoo.cosid.snowflake.SnowflakeIdStateParser; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; +import org.apache.shardingsphere.sharding.cosid.algorithm.keygen.CosIdSnowflakeKeyGenerateAlgorithm; + +import java.time.ZoneId; +import java.util.Properties; + +/** + * Snowflake interval sharding algorithm with CosId. + */ +public final class CosIdSnowflakeIntervalShardingAlgorithm extends AbstractCosIdIntervalShardingAlgorithm<Comparable<?>> { + + private static final String EPOCH_KEY = "epoch"; + + private static final String ZONE_ID_KEY = "zone-id"; + + @Override + protected LocalDateTimeConvertor createLocalDateTimeConvertor(final Properties props) { + return new SnowflakeLocalDateTimeConvertor(createSnowflakeIdStateParser(props)); + } + + private SnowflakeIdStateParser createSnowflakeIdStateParser(final Properties props) { + long epoch = Long.parseLong(props.getProperty(EPOCH_KEY, String.valueOf(CosIdSnowflakeKeyGenerateAlgorithm.DEFAULT_EPOCH))); + ZoneId zoneId = props.containsKey(ZONE_ID_KEY) ? ZoneId.of(props.getProperty(ZONE_ID_KEY)) : ZoneId.systemDefault(); + return new MillisecondSnowflakeIdStateParser( + epoch, MillisecondSnowflakeId.DEFAULT_TIMESTAMP_BIT, MillisecondSnowflakeId.DEFAULT_MACHINE_BIT, MillisecondSnowflakeId.DEFAULT_SEQUENCE_BIT, zoneId); + } + + @Override + public String getType() { + return CosIdAlgorithmConstants.TYPE_PREFIX + "INTERVAL_SNOWFLAKE"; + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/mod/CosIdModShardingAlgorithm.java b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/mod/CosIdModShardingAlgorithm.java new file mode 100644 index 0000000..6167b08 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/java/org/apache/shardingsphere/sharding/cosid/algorithm/sharding/mod/CosIdModShardingAlgorithm.java @@ -0,0 +1,69 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.sharding.mod; + +import me.ahoo.cosid.sharding.ModCycle; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; +import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; +import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; +import org.apache.shardingsphere.sharding.exception.ShardingPluginException; + +import java.util.Collection; +import java.util.Properties; + +/** + * Modular sharding algorithm with CosId. + * + * @param <T> type of sharding value + */ +public final class CosIdModShardingAlgorithm<T extends Number & Comparable<T>> implements StandardShardingAlgorithm<T> { + + private static final String MODULO_KEY = "mod"; + + private ModCycle<T> modCycle; + + @Override + public void init(final Properties props) { + String divisorStr = getRequiredValue(props, MODULO_KEY); + int divisor = Integer.parseInt(divisorStr); + String logicNamePrefix = getRequiredValue(props, CosIdAlgorithmConstants.LOGIC_NAME_PREFIX_KEY); + modCycle = new ModCycle<>(divisor, logicNamePrefix); + } + + private String getRequiredValue(final Properties props, final String key) { + ShardingSpherePreconditions.checkState(props.containsKey(key), () -> new ShardingPluginException("%s can not be null.", key)); + return props.getProperty(key); + } + + @Override + public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<T> shardingValue) { + return modCycle.sharding(shardingValue.getValue()); + } + + @Override + public Collection<String> doSharding(final Collection<String> availableTargetNames, final RangeShardingValue<T> shardingValue) { + return modCycle.sharding(shardingValue.getValueRange()); + } + + @Override + public String getType() { + return CosIdAlgorithmConstants.TYPE_PREFIX + "MOD"; + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm b/feature-plugin/sharding-plugin/cosid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm new file mode 100644 index 0000000..7ada141 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm @@ -0,0 +1,19 @@ +# +# 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. +# + +org.apache.shardingsphere.sharding.cosid.algorithm.keygen.CosIdKeyGenerateAlgorithm +org.apache.shardingsphere.sharding.cosid.algorithm.keygen.CosIdSnowflakeKeyGenerateAlgorithm diff --git a/feature-plugin/sharding-plugin/cosid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.ShardingAlgorithm b/feature-plugin/sharding-plugin/cosid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.ShardingAlgorithm new file mode 100644 index 0000000..2198fe2 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.ShardingAlgorithm @@ -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. +# + +org.apache.shardingsphere.sharding.cosid.algorithm.sharding.mod.CosIdModShardingAlgorithm +org.apache.shardingsphere.sharding.cosid.algorithm.sharding.interval.CosIdIntervalShardingAlgorithm +org.apache.shardingsphere.sharding.cosid.algorithm.sharding.interval.CosIdSnowflakeIntervalShardingAlgorithm diff --git a/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdKeyGenerateAlgorithmTest.java b/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdKeyGenerateAlgorithmTest.java new file mode 100644 index 0000000..1368261 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdKeyGenerateAlgorithmTest.java @@ -0,0 +1,85 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.keygen; + +import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.StringIdGeneratorDecorator; +import me.ahoo.cosid.converter.PrefixIdConverter; +import me.ahoo.cosid.converter.Radix62IdConverter; +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.provider.NotFoundIdGeneratorException; +import me.ahoo.cosid.segment.DefaultSegmentId; +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.sharding.cosid.algorithm.CosIdAlgorithmConstants; +import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm; +import org.apache.shardingsphere.test.util.PropertiesBuilder; +import org.apache.shardingsphere.test.util.PropertiesBuilder.Property; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CosIdKeyGenerateAlgorithmTest { + + @Test + void assertGenerateKey() { + String idName = "test-cosid"; + DefaultSegmentId defaultSegmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + DefaultIdGeneratorProvider.INSTANCE.set(idName, defaultSegmentId); + KeyGenerateAlgorithm algorithm = TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID", PropertiesBuilder.build(new Property(CosIdAlgorithmConstants.ID_NAME_KEY, idName))); + assertThat(algorithm.generateKey(), is(1L)); + assertThat(algorithm.generateKey(), is(2L)); + } + + @Test + void assertGenerateKeyWhenNotSetIdName() { + DefaultSegmentId defaultSegmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + DefaultIdGeneratorProvider.INSTANCE.setShare(defaultSegmentId); + KeyGenerateAlgorithm algorithm = TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID"); + assertThat(algorithm.generateKey(), is(1L)); + assertThat(algorithm.generateKey(), is(2L)); + } + + @Test + void assertGenerateKeyWhenIdProviderIsEmpty() { + DefaultIdGeneratorProvider.INSTANCE.clear(); + assertThrows(NotFoundIdGeneratorException.class, () -> TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID").generateKey()); + } + + @Test + void assertGenerateKeyAsString() { + String idName = "test-cosid-as-string"; + String prefix = "test_"; + IdGenerator idGeneratorDecorator = new StringIdGeneratorDecorator(new MillisecondSnowflakeId(1, 0), new PrefixIdConverter(prefix, Radix62IdConverter.INSTANCE)); + DefaultIdGeneratorProvider.INSTANCE.set(idName, idGeneratorDecorator); + Properties props = PropertiesBuilder.build(new Property(CosIdAlgorithmConstants.ID_NAME_KEY, idName), new Property("as-string", Boolean.TRUE.toString())); + KeyGenerateAlgorithm algorithm = TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID", props); + Comparable<?> actual = algorithm.generateKey(); + assertThat(actual, instanceOf(String.class)); + assertThat(actual.toString(), startsWith(prefix)); + assertThat(actual.toString().length(), lessThanOrEqualTo(16)); + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdSnowflakeKeyGenerateAlgorithmTest.java b/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdSnowflakeKeyGenerateAlgorithmTest.java new file mode 100644 index 0000000..4c1b9ab --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/CosIdSnowflakeKeyGenerateAlgorithmTest.java @@ -0,0 +1,160 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.keygen; + +import me.ahoo.cosid.converter.Radix62IdConverter; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeIdStateParser; +import me.ahoo.cosid.snowflake.SnowflakeIdState; +import me.ahoo.cosid.snowflake.SnowflakeIdStateParser; +import org.apache.shardingsphere.infra.config.mode.ModeConfiguration; +import org.apache.shardingsphere.infra.instance.ComputeNodeInstance; +import org.apache.shardingsphere.infra.instance.InstanceContext; +import org.apache.shardingsphere.infra.instance.metadata.InstanceMetaData; +import org.apache.shardingsphere.infra.instance.mode.ModeContextManager; +import org.apache.shardingsphere.infra.lock.LockContext; +import org.apache.shardingsphere.infra.util.eventbus.EventBusContext; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.sharding.cosid.algorithm.keygen.fixture.WorkerIdGeneratorFixture; +import org.apache.shardingsphere.sharding.exception.ShardingPluginException; +import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm; +import org.apache.shardingsphere.test.util.PropertiesBuilder; +import org.apache.shardingsphere.test.util.PropertiesBuilder.Property; +import org.junit.jupiter.api.Test; + +import java.util.Properties; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.locks.LockSupport; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.closeTo; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +class CosIdSnowflakeKeyGenerateAlgorithmTest { + + private static final int FIXTURE_WORKER_ID = 0; + + private final SnowflakeIdStateParser snowflakeIdStateParser = new MillisecondSnowflakeIdStateParser( + CosIdSnowflakeKeyGenerateAlgorithm.DEFAULT_EPOCH, + MillisecondSnowflakeId.DEFAULT_TIMESTAMP_BIT, + MillisecondSnowflakeId.DEFAULT_MACHINE_BIT, + MillisecondSnowflakeId.DEFAULT_SEQUENCE_BIT); + + private final EventBusContext eventBusContext = new EventBusContext(); + + @Test + void assertGenerateKey() { + CosIdSnowflakeKeyGenerateAlgorithm algorithm = (CosIdSnowflakeKeyGenerateAlgorithm) TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE"); + algorithm.setInstanceContext(new InstanceContext(new ComputeNodeInstance(mock(InstanceMetaData.class)), new WorkerIdGeneratorFixture(FIXTURE_WORKER_ID), + new ModeConfiguration("Standalone", null), mock(ModeContextManager.class), mock(LockContext.class), eventBusContext)); + long firstActualKey = (Long) algorithm.generateKey(); + long secondActualKey = (Long) algorithm.generateKey(); + SnowflakeIdState firstActualState = snowflakeIdStateParser.parse(firstActualKey); + SnowflakeIdState secondActualState = snowflakeIdStateParser.parse(secondActualKey); + assertThat(firstActualState.getMachineId(), is(FIXTURE_WORKER_ID)); + assertThat(firstActualState.getSequence(), is(1L)); + assertThat(secondActualState.getMachineId(), is(FIXTURE_WORKER_ID)); + long expectedSecondSequence = 2L; + assertThat(secondActualState.getSequence(), is(expectedSecondSequence)); + } + + @Test + void assertGenerateKeyModUniformity() { + CosIdSnowflakeKeyGenerateAlgorithm algorithm = (CosIdSnowflakeKeyGenerateAlgorithm) TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE"); + algorithm.setInstanceContext(new InstanceContext(new ComputeNodeInstance(mock(InstanceMetaData.class)), new WorkerIdGeneratorFixture(FIXTURE_WORKER_ID), + new ModeConfiguration("Standalone", null), mock(ModeContextManager.class), mock(LockContext.class), eventBusContext)); + int divisor = 4; + int total = 99999; + int avg = total / divisor; + double tolerance = avg * .0015; + int mod0Counter = 0; + int mod1Counter = 0; + int mod2Counter = 0; + int mod3Counter = 0; + for (int i = 0; i < total; i++) { + long id = (Long) algorithm.generateKey(); + int mod = (int) (id % divisor); + switch (mod) { + case 0: + mod0Counter++; + break; + case 1: + mod1Counter++; + break; + case 2: + mod2Counter++; + break; + case 3: + mod3Counter++; + break; + default: + throw new IllegalStateException("Unexpected value: " + mod); + } + int wait = ThreadLocalRandom.current().nextInt(10, 1000); + LockSupport.parkNanos(wait); + } + assertThat((double) mod0Counter, closeTo(avg, tolerance)); + assertThat((double) mod1Counter, closeTo(avg, tolerance)); + assertThat((double) mod2Counter, closeTo(avg, tolerance)); + assertThat((double) mod3Counter, closeTo(avg, tolerance)); + } + + @Test + void assertGenerateKeyAsString() { + Properties props = PropertiesBuilder.build(new Property(CosIdSnowflakeKeyGenerateAlgorithm.AS_STRING_KEY, Boolean.TRUE.toString())); + CosIdSnowflakeKeyGenerateAlgorithm algorithm = (CosIdSnowflakeKeyGenerateAlgorithm) TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE", props); + algorithm.setInstanceContext(new InstanceContext(new ComputeNodeInstance(mock(InstanceMetaData.class)), + new WorkerIdGeneratorFixture(FIXTURE_WORKER_ID), new ModeConfiguration("Standalone", null), + mock(ModeContextManager.class), mock(LockContext.class), eventBusContext)); + Comparable<?> actualKey = algorithm.generateKey(); + assertThat(actualKey, instanceOf(String.class)); + String actualStringKey = (String) actualKey; + assertThat(actualStringKey.length(), is(Radix62IdConverter.MAX_CHAR_SIZE)); + long actualLongKey = Radix62IdConverter.PAD_START.asLong(actualStringKey); + SnowflakeIdState actualState = snowflakeIdStateParser.parse(actualLongKey); + assertThat(actualState.getMachineId(), is(FIXTURE_WORKER_ID)); + assertThat(actualState.getSequence(), is(1L)); + } + + @Test + void assertGenerateKeyWhenNoneInstanceContext() { + assertThrows(ShardingPluginException.class, () -> TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE").generateKey()); + } + + @Test + void assertGenerateKeyWhenNegative() { + CosIdSnowflakeKeyGenerateAlgorithm algorithm = (CosIdSnowflakeKeyGenerateAlgorithm) TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE"); + assertThrows(IllegalArgumentException.class, () -> algorithm.setInstanceContext(new InstanceContext(new ComputeNodeInstance(mock(InstanceMetaData.class)), new WorkerIdGeneratorFixture(-1), + new ModeConfiguration("Standalone", null), mock(ModeContextManager.class), mock(LockContext.class), eventBusContext))); + } + + @Test + void assertGenerateKeyWhenGreaterThen1023() { + CosIdSnowflakeKeyGenerateAlgorithm algorithm = (CosIdSnowflakeKeyGenerateAlgorithm) TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE"); + assertThrows(IllegalArgumentException.class, () -> algorithm.setInstanceContext(new InstanceContext(new ComputeNodeInstance(mock(InstanceMetaData.class)), new WorkerIdGeneratorFixture(1024), + new ModeConfiguration("Standalone", null), mock(ModeContextManager.class), mock(LockContext.class), eventBusContext))); + } + + @Test + void assertEpochWhenOutOfRange() { + assertThrows(ShardingPluginException.class, () -> TypedSPILoader.getService(KeyGenerateAlgorithm.class, "COSID_SNOWFLAKE", PropertiesBuilder.build(new Property("epoch", "0"))).generateKey()); + } +} diff --git a/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/fixture/WorkerIdGeneratorFixture.java b/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/fixture/WorkerIdGeneratorFixture.java new file mode 100644 index 0000000..1587f46 --- /dev/null +++ b/feature-plugin/sharding-plugin/cosid/src/test/java/org/apache/shardingsphere/sharding/cosid/algorithm/keygen/fixture/WorkerIdGeneratorFixture.java @@ -0,0 +1,34 @@ +/* + * 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.shardingsphere.sharding.cosid.algorithm.keygen.fixture; + +import lombok.RequiredArgsConstructor; +import org.apache.shardingsphere.infra.instance.workerid.WorkerIdGenerator; + +import java.util.Properties; + +@RequiredArgsConstructor +public final class WorkerIdGeneratorFixture implements WorkerIdGenerator { + + private final int fixtureWorkerId; + + @Override + public int generate(final Properties props) { + return fixtureWorkerId; + } +} diff --git a/feature-plugin/sharding-plugin/nanoid/pom.xml b/feature-plugin/sharding-plugin/nanoid/pom.xml new file mode 100644 index 0000000..d4739fc --- /dev/null +++ b/feature-plugin/sharding-plugin/nanoid/pom.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-sharding-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + </parent> + <artifactId>shardingsphere-sharding-plugin-nanoid</artifactId> + <name>${project.artifactId}</name> + + <properties> + <jnanoid.version>2.0.0</jnanoid.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-sharding-api</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.aventrix.jnanoid</groupId> + <artifactId>jnanoid</artifactId> + <version>${jnanoid.version}</version> + </dependency> + </dependencies> +</project> diff --git a/feature-plugin/sharding-plugin/nanoid/src/main/java/org/apache/shardingsphere/sharding/nanoid/algorithm/keygen/NanoIdKeyGenerateAlgorithm.java b/feature-plugin/sharding-plugin/nanoid/src/main/java/org/apache/shardingsphere/sharding/nanoid/algorithm/keygen/NanoIdKeyGenerateAlgorithm.java new file mode 100644 index 0000000..4ff26b9 --- /dev/null +++ b/feature-plugin/sharding-plugin/nanoid/src/main/java/org/apache/shardingsphere/sharding/nanoid/algorithm/keygen/NanoIdKeyGenerateAlgorithm.java @@ -0,0 +1,39 @@ +/* + * 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.shardingsphere.sharding.nanoid.algorithm.keygen; + +import com.aventrix.jnanoid.jnanoid.NanoIdUtils; +import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * NanoId key generate algorithm. + */ +public final class NanoIdKeyGenerateAlgorithm implements KeyGenerateAlgorithm { + + @Override + public String generateKey() { + return NanoIdUtils.randomNanoId(ThreadLocalRandom.current(), NanoIdUtils.DEFAULT_ALPHABET, NanoIdUtils.DEFAULT_SIZE); + } + + @Override + public String getType() { + return "NANOID"; + } +} diff --git a/feature-plugin/sharding-plugin/nanoid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm b/feature-plugin/sharding-plugin/nanoid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm new file mode 100644 index 0000000..759153d --- /dev/null +++ b/feature-plugin/sharding-plugin/nanoid/src/main/resources/META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm @@ -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. +# + +org.apache.shardingsphere.sharding.nanoid.algorithm.keygen.NanoIdKeyGenerateAlgorithm diff --git a/feature-plugin/sharding-plugin/nanoid/src/test/java/org/apache/shardingsphere/sharding/nanoid/algorithm/keygen/NanoIdKeyGenerateAlgorithmTest.java b/feature-plugin/sharding-plugin/nanoid/src/test/java/org/apache/shardingsphere/sharding/nanoid/algorithm/keygen/NanoIdKeyGenerateAlgorithmTest.java new file mode 100644 index 0000000..f662011 --- /dev/null +++ b/feature-plugin/sharding-plugin/nanoid/src/test/java/org/apache/shardingsphere/sharding/nanoid/algorithm/keygen/NanoIdKeyGenerateAlgorithmTest.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.shardingsphere.sharding.nanoid.algorithm.keygen; + +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class NanoIdKeyGenerateAlgorithmTest { + + @Test + void assertGenerateKey() { + assertThat(TypedSPILoader.getService(KeyGenerateAlgorithm.class, "NANOID").generateKey().toString().length(), is(21)); + } +} diff --git a/feature-plugin/sharding-plugin/pom.xml b/feature-plugin/sharding-plugin/pom.xml new file mode 100644 index 0000000..f80aa8f --- /dev/null +++ b/feature-plugin/sharding-plugin/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-feature-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + </parent> + <artifactId>shardingsphere-feature-sharding-plugin</artifactId> + <packaging>pom</packaging> + <name>${project.artifactId}</name> + + <modules> + <module>cosid</module> + <module>nanoid</module> + </modules> +</project> diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2d65dfe --- /dev/null +++ b/pom.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache</groupId> + <artifactId>apache</artifactId> + <version>29</version> + </parent> + <groupId>org.apache.shardingsphere</groupId> + <artifactId>shardingsphere-plugin</artifactId> + <version>5.4.1-SNAPSHOT</version> + <packaging>pom</packaging> + <name>Apache ShardingSphere Plugin</name> + <description>Provide plugins for ShardingSphere pluggable architecture</description> + + <modules> + <module>feature-plugin</module> + </modules> + + <properties> + <!-- Environments --> + <java.version>1.8</java.version> + <maven.version.range>[3.0.4,)</maven.version.range> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.deploy.skip>false</maven.deploy.skip> + <maven.javadoc.skip>true</maven.javadoc.skip> + <checkstyle.skip>true</checkstyle.skip> + <rat.skip>true</rat.skip> + + <!-- 3rd party library versions --> + <guava.version>30.0-jre</guava.version> + <lombok.version>1.18.20</lombok.version> + <junit.version>5.9.2</junit.version> + <hamcrest.version>2.2</hamcrest.version> + <mockito.version>4.11.0</mockito.version> + + <!-- Compile plugin versions --> + <maven-enforcer-plugin.version>3.2.1</maven-enforcer-plugin.version> + <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> + <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version> + <maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version> + <maven-jar-plugin.version>3.3.0</maven-jar-plugin.version> + </properties> + + <dependencyManagement> + <dependencies> + </dependencies> + </dependencyManagement> +</project>
