From 0258f0bcaa9df7b5912adbd3a33d7f52ad976c17 Mon Sep 17 00:00:00 2001 From: MatrixEditor <58256046+MatrixEditor@users.noreply.github.com> Date: Sun, 10 Dec 2023 16:30:16 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 26 ++ CHANGELOG.md | 5 + LICENSE | 21 ++ README.md | 54 ++++ .../redis/mysql/MySQLConfiguration.java | 74 +++++ .../org/proto4j/redis/mysql/MySQLContext.java | 68 ++++ .../org/proto4j/redis/mysql/MySQLFactory.java | 105 +++++++ .../org/proto4j/redis/mysql/MySQLService.java | 86 +++++ .../org/proto4j/redis/mysql/MySQLSource.java | 89 ++++++ .../services/org.proto4j.redis.sql.SQLFactory | 25 ++ .../src/main/resources/redis-mysql.properties | 2 + .../redis/sqlite/SQLiteConfiguration.java | 53 ++++ .../proto4j/redis/sqlite/SQLiteContext.java | 68 ++++ .../proto4j/redis/sqlite/SQLiteFactory.java | 105 +++++++ .../proto4j/redis/sqlite/SQLiteService.java | 87 ++++++ .../proto4j/redis/sqlite/SQLiteSource.java | 89 ++++++ .../services/org.proto4j.redis.sql.SQLFactory | 25 ++ .../main/resources/redis-sqlite.properties | 2 + src/main/java/module-info.java | 42 +++ .../java/org/proto4j/redis/Extractors.java | 293 ++++++++++++++++++ .../org/proto4j/redis/FactoryManager.java | 220 +++++++++++++ .../org/proto4j/redis/InstanceCreator.java | 149 +++++++++ .../java/org/proto4j/redis/MethodBuilder.java | 142 +++++++++ src/main/java/org/proto4j/redis/Redis.java | 204 ++++++++++++ .../org/proto4j/redis/RedisExtractor.java | 56 ++++ .../java/org/proto4j/redis/SQLMethod.java | 158 ++++++++++ .../java/org/proto4j/redis/package-info.java | 41 +++ .../java/org/proto4j/redis/sql/Entity.java | 40 +++ .../java/org/proto4j/redis/sql/Param.java | 61 ++++ src/main/java/org/proto4j/redis/sql/SQL.java | 284 +++++++++++++++++ .../proto4j/redis/sql/SQLConfiguration.java | 135 ++++++++ .../org/proto4j/redis/sql/SQLContext.java | 70 +++++ .../org/proto4j/redis/sql/SQLExtractor.java | 48 +++ .../org/proto4j/redis/sql/SQLFactory.java | 114 +++++++ .../org/proto4j/redis/sql/SQLPrincipal.java | 114 +++++++ .../org/proto4j/redis/sql/SQLService.java | 137 ++++++++ .../java/org/proto4j/redis/sql/SQLSource.java | 46 +++ .../org/proto4j/redis/sql/SQLValidator.java | 44 +++ .../java/org/proto4j/redis/sql/Validator.java | 44 +++ .../org/proto4j/test/redis/SqliteTest.java | 44 +++ .../test/org/proto4j/test/redis/User.java | 43 +++ .../org/proto4j/test/redis/UserStorage.java | 40 +++ 42 files changed, 3553 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLConfiguration.java create mode 100644 factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLContext.java create mode 100644 factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLFactory.java create mode 100644 factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLService.java create mode 100644 factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLSource.java create mode 100644 factories/redis-mysql/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory create mode 100644 factories/redis-mysql/src/main/resources/redis-mysql.properties create mode 100644 factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteConfiguration.java create mode 100644 factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteContext.java create mode 100644 factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteFactory.java create mode 100644 factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteService.java create mode 100644 factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteSource.java create mode 100644 factories/redis-sqlite/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory create mode 100644 factories/redis-sqlite/src/main/resources/redis-sqlite.properties create mode 100644 src/main/java/module-info.java create mode 100644 src/main/java/org/proto4j/redis/Extractors.java create mode 100644 src/main/java/org/proto4j/redis/FactoryManager.java create mode 100644 src/main/java/org/proto4j/redis/InstanceCreator.java create mode 100644 src/main/java/org/proto4j/redis/MethodBuilder.java create mode 100644 src/main/java/org/proto4j/redis/Redis.java create mode 100644 src/main/java/org/proto4j/redis/RedisExtractor.java create mode 100644 src/main/java/org/proto4j/redis/SQLMethod.java create mode 100644 src/main/java/org/proto4j/redis/package-info.java create mode 100644 src/main/java/org/proto4j/redis/sql/Entity.java create mode 100644 src/main/java/org/proto4j/redis/sql/Param.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQL.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLConfiguration.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLContext.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLExtractor.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLFactory.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLPrincipal.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLService.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLSource.java create mode 100644 src/main/java/org/proto4j/redis/sql/SQLValidator.java create mode 100644 src/main/java/org/proto4j/redis/sql/Validator.java create mode 100644 src/main/test/org/proto4j/test/redis/SqliteTest.java create mode 100644 src/main/test/org/proto4j/test/redis/User.java create mode 100644 src/main/test/org/proto4j/test/redis/UserStorage.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f2273c --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ +.idea/ +*.iml +docs/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1d1f212 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log + +## Version 1.0.0 + +Initial release. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aa16b06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Proto4j + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d904bc --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Proto4j-Redis + +A type-safe framework to turn a java interface into a database worker. For more information please visit [the documentation](https://proto4j.github.io/proto4j-redis/). + +This repository contains the source code for the `Redis` module from `Proto4j`. It is considered to be a development repository where changes can be made and features can be requested. `SQLFactory` implementation modules are provided in the `factories` directory. Currently, only SQLite and MySQL are provided. + +# Example usage + +This framework is built to be user-friendly when working with generated service workers. The following example illustrates a simple user service: + +````java +@SQL(SQLiteFactory.FACTORY) +public interface UserDao { + @SQL.Select("select * from {table};") + public List fetchUsers(@Param("table") String tableName); +} + +public static void main(String[]args) { + SQLiteConfiguration config = new SQLiteConfiguration("mydb"); + UserDao userDao = Redis.create(UserDao.class, config); + // fetch all user objects + for (User user : userDao.fetchUsers("user_table1")) { + // ... + } +} +```` + +## Download + +Download the [latest JAR file](https://github.com/Proto4j/proto4j-redis/releases) from the releases tab and the `SQLFactory` implementation JAR file what you intend to use. This framework requires a minimum of Java 8+ for developing and running. + +## License + + MIT License + + Copyright (c) 2022 Proto4j + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLConfiguration.java b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLConfiguration.java new file mode 100644 index 0000000..cf60a31 --- /dev/null +++ b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLConfiguration.java @@ -0,0 +1,74 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.mysql; //@date 03.09.2022 + +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sql.SQLPrincipal; + +import java.util.Objects; +import java.util.Properties; + +public class MySQLConfiguration extends SQLConfiguration { + + private final String host; + private final int port; + private final String database; + + public MySQLConfiguration(String host, int port, String database) { + this(null, host, port, database); + } + + public MySQLConfiguration(SQLPrincipal principal, + String host, int port, String database) { + this(null, principal, host, port, database); + } + + public MySQLConfiguration(Properties properties, SQLPrincipal principal, + String host, int port, String database) { + super(MySQLFactory.FACTORY, properties, principal); + this.host = Objects.requireNonNull(host); + this.port = port; + this.database = Objects.requireNonNull(database); + } + + + @Override + public String getQualifiedName() { + String path = "//" + host + ':' + port + '/' + database; + return String.join(":", "jdbc", getDriverType(), path); + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getDatabase() { + return database; + } +} diff --git a/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLContext.java b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLContext.java new file mode 100644 index 0000000..e64e94d --- /dev/null +++ b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLContext.java @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.mysql; //@date 03.09.2022 + +import org.proto4j.redis.sql.SQLContext; +import org.proto4j.redis.sql.SQLSource; + +public class MySQLContext implements SQLContext { + + private final SQLSource source; + + private Class parent; + private Object reference; + + public MySQLContext(SQLSource source) {this.source = source;} + + @Override + public SQLSource getSource() { + return source; + } + + @Override + public Class getAPIServiceType() { + return MySQLService.class; + } + + @Override + public Object getReference() { + return reference; + } + + @Override + public void setReference(Object reference) { + this.reference = reference; + } + + @Override + public Class getReferenceParent() { + return parent; + } + + @Override + public void setReferenceParent(Class cls) { + this.parent = cls; + } +} diff --git a/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLFactory.java b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLFactory.java new file mode 100644 index 0000000..036568d --- /dev/null +++ b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLFactory.java @@ -0,0 +1,105 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.mysql; //@date 03.09.2022 + +import org.proto4j.redis.FactoryManager; +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sql.SQLFactory; +import org.proto4j.redis.sql.SQLService; +import org.proto4j.redis.sql.SQLSource; + +import java.io.IOException; +import java.net.URL; +import java.sql.DriverManager; +import java.sql.SQLDataException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.util.Properties; + +public class MySQLFactory implements SQLFactory { + + public static final String FACTORY = "mysql"; + + static { + try { + // This factory should work only if the MySQL-Driver + // is registered. Otherwise, this service can not be called + // because the corresponding driver is missing. + DriverManager.getDriver("jdbc:mysql:temp"); + + FactoryManager.registerFactory(new MySQLFactory()); + } catch (SQLException e) { + System.err.println("WARNING: no suitable driver for jdbc:mysql"); + } + } + + public MySQLFactory() { + } + + @Override + public SQLSource engineGetSource(SQLConfiguration conf) throws SQLDataException, SQLWarning { + return new MySQLSource(conf); + } + + @Override + public SQLService engineGetService(SQLSource source) throws SQLException { + return new MySQLService(source); + } + + @Override + public String engineDriverType() { + return FACTORY; + } + + @Override + public int getMajorVersion() { + String[] vInfo = getVersion().split("\\."); + return vInfo.length > 1 ? Integer.parseInt(vInfo[1]) : 1; + } + + @Override + public int getMinorVersion() { + String[] vInfo = getVersion().split("\\."); + return vInfo.length > 2 ? Integer.parseInt(vInfo[2]) : 0; + } + + public static String getVersion() { + URL versionFile = MySQLFactory.class.getResource("/redis-sqlite.properties"); + if (versionFile == null) { + throw new UnsupportedClassVersionError("Could not examine version"); + } + + String version = "unknown"; + try { + Properties data = new Properties(); + data.load(versionFile.openStream()); + version = data.getProperty("version", version); + version = version.trim(); + } catch (IOException e) { + System.err.println(e.getMessage()); + } + return version; + } +} diff --git a/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLService.java b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLService.java new file mode 100644 index 0000000..42c8a34 --- /dev/null +++ b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLService.java @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.mysql; //@date 03.09.2022 + +import org.proto4j.redis.sql.*; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Objects; + +public class MySQLService extends SQLService { + + private final SQLContext context; + + public MySQLService(SQLSource source) { + super(source); + context = new MySQLContext(source); + } + + @Override + public T select(String sql, SQLExtractor extractor) throws SQLException { + return rundml(sql, extractor); + } + + @Override + public boolean insert(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public boolean update(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public boolean create(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public boolean drop(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public Object raw(String sql) throws SQLException { + return rundml(sql, null) == null; + } + + private T rundml(String sql, SQLExtractor extractor) throws SQLException { + Objects.requireNonNull(sql); + + try (PreparedStatement pst = getSource().prepare(sql)) { + Objects.requireNonNull(pst); + + if (pst.execute()) { + if (extractor != null) { + return extractor.read(pst.getResultSet(), context); + } + } + return null; + } + } +} diff --git a/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLSource.java b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLSource.java new file mode 100644 index 0000000..59867d0 --- /dev/null +++ b/factories/redis-mysql/src/main/java/org/proto4j/redis/mysql/MySQLSource.java @@ -0,0 +1,89 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.mysql; //@date 03.09.2022 + +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sql.SQLPrincipal; +import org.proto4j.redis.sql.SQLSource; + +import java.security.Principal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Objects; + +// same as DefaultSQLSource +public class MySQLSource extends SQLSource { + + private volatile Connection connection; + + protected MySQLSource(SQLConfiguration configuration) { + super(configuration); + } + + @Override + public Connection getConnection() throws SQLException { + if (connection != null) { + return connection; + } + + String url = getConfiguration().getQualifiedName(); + Objects.requireNonNull(url); + + if (getConfiguration().getPrincipal() != null) { + Principal principal = getConfiguration().getPrincipal(); + if (principal instanceof SQLPrincipal) { + SQLPrincipal up = ((SQLPrincipal) principal); + connection = DriverManager.getConnection(url, principal.getName(), new String(up.getPassword())); + up.destroy(); + } + } else { + if (getConfiguration().getProperties() != null) { + connection = DriverManager.getConnection(url, getConfiguration().getProperties()); + } else { + connection = DriverManager.getConnection(url); + } + } + return connection; + } + + @Override + public PreparedStatement prepare(String sql) throws SQLException { + Connection c = getConnection(); + if (connection == null) { + c = getConnection(); + } + if (c == null || sql == null || sql.isEmpty()) { + throw new SQLException("Statement is null"); + } + return c.prepareStatement(sql); + } + + @Override + public boolean isConnected() { + return connection != null; + } +} diff --git a/factories/redis-mysql/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory b/factories/redis-mysql/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory new file mode 100644 index 0000000..9c32385 --- /dev/null +++ b/factories/redis-mysql/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory @@ -0,0 +1,25 @@ +# +# MIT License +# +# Copyright (c) 2022 Proto4j +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +org.proto4j.redis.mysql.MySQLFactory \ No newline at end of file diff --git a/factories/redis-mysql/src/main/resources/redis-mysql.properties b/factories/redis-mysql/src/main/resources/redis-mysql.properties new file mode 100644 index 0000000..a492b8f --- /dev/null +++ b/factories/redis-mysql/src/main/resources/redis-mysql.properties @@ -0,0 +1,2 @@ +name=Redis MySQL +version=1.5.2 \ No newline at end of file diff --git a/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteConfiguration.java b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteConfiguration.java new file mode 100644 index 0000000..0e5c13b --- /dev/null +++ b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteConfiguration.java @@ -0,0 +1,53 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sqlite; //@date 03.09.2022 + +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sql.SQLPrincipal; + +import java.util.Objects; + +public class SQLiteConfiguration extends SQLConfiguration { + + private final String path; + + public SQLiteConfiguration(String path) { + this(null, path); + } + + public SQLiteConfiguration(SQLPrincipal principal, String path) { + super(SQLiteFactory.FACTORY, null, principal); + this.path = Objects.requireNonNull(path); + } + + @Override + public String getQualifiedName() { + return String.join(":", "jdbc", getDriverType(), path); + } + + public String getPath() { + return path; + } +} diff --git a/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteContext.java b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteContext.java new file mode 100644 index 0000000..44d384d --- /dev/null +++ b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteContext.java @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sqlite; //@date 03.09.2022 + +import org.proto4j.redis.sql.SQLContext; +import org.proto4j.redis.sql.SQLSource; + +public class SQLiteContext implements SQLContext { + + private final SQLSource source; + + private Class parent; + private Object reference; + + public SQLiteContext(SQLSource source) {this.source = source;} + + @Override + public SQLSource getSource() { + return source; + } + + @Override + public Class getAPIServiceType() { + return SQLiteService.class; + } + + @Override + public Object getReference() { + return reference; + } + + @Override + public void setReference(Object reference) { + this.reference = reference; + } + + @Override + public Class getReferenceParent() { + return parent; + } + + @Override + public void setReferenceParent(Class cls) { + this.parent = cls; + } +} diff --git a/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteFactory.java b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteFactory.java new file mode 100644 index 0000000..f077c64 --- /dev/null +++ b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteFactory.java @@ -0,0 +1,105 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sqlite; //@date 03.09.2022 + +import org.proto4j.redis.FactoryManager; +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sql.SQLFactory; +import org.proto4j.redis.sql.SQLService; +import org.proto4j.redis.sql.SQLSource; + +import java.io.IOException; +import java.net.URL; +import java.sql.DriverManager; +import java.sql.SQLDataException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.util.Properties; + +public class SQLiteFactory implements SQLFactory { + + public static final String FACTORY = "sqlite"; + + static { + try { + // This factory should work only if the SQLite-Driver + // is registered. Otherwise, this service can not be called + // because the corresponding driver is missing. + DriverManager.getDriver("jdbc:sqlite:temp"); + + FactoryManager.registerFactory(new SQLiteFactory()); + } catch (SQLException e) { + System.err.println("WARNING: no suitable driver for jdbc:sqlite"); + } + } + + public SQLiteFactory() { + } + + @Override + public SQLSource engineGetSource(SQLConfiguration conf) throws SQLDataException, SQLWarning { + return new SQLiteSource(conf); + } + + @Override + public SQLService engineGetService(SQLSource source) throws SQLException { + return new SQLiteService(source); + } + + @Override + public String engineDriverType() { + return FACTORY; + } + + @Override + public int getMajorVersion() { + String[] vInfo = getVersion().split("\\."); + return vInfo.length > 1 ? Integer.parseInt(vInfo[1]) : 1; + } + + @Override + public int getMinorVersion() { + String[] vInfo = getVersion().split("\\."); + return vInfo.length > 2 ? Integer.parseInt(vInfo[2]) : 0; + } + + public static String getVersion() { + URL versionFile = SQLiteFactory.class.getResource("/redis-sqlite.properties"); + if (versionFile == null) { + throw new UnsupportedClassVersionError("Could not examine version"); + } + + String version = "unknown"; + try { + Properties data = new Properties(); + data.load(versionFile.openStream()); + version = data.getProperty("version", version); + version = version.trim(); + } catch (IOException e) { + System.err.println(e.getMessage()); + } + return version; + } +} diff --git a/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteService.java b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteService.java new file mode 100644 index 0000000..b45705b --- /dev/null +++ b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteService.java @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sqlite; //@date 03.09.2022 + +import org.proto4j.redis.sql.*; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; + +public class SQLiteService extends SQLService { + + private final SQLContext context; + + public SQLiteService(SQLSource source) { + super(source); + context = new SQLiteContext(source); + } + + @Override + public T select(String sql, SQLExtractor extractor) throws SQLException { + return rundml(sql, extractor); + } + + @Override + public boolean insert(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public boolean update(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public boolean create(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public boolean drop(String sql) throws SQLException { + return (boolean) raw(sql); + } + + @Override + public Object raw(String sql) throws SQLException { + return rundml(sql, null) == null; + } + + private T rundml(String sql, SQLExtractor extractor) throws SQLException { + Objects.requireNonNull(sql); + + try (PreparedStatement pst = getSource().prepare(sql)) { + Objects.requireNonNull(pst); + + if (pst.execute()) { + if (extractor != null) { + return extractor.read(pst.getResultSet(), context); + } + } + return null; + } + } +} diff --git a/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteSource.java b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteSource.java new file mode 100644 index 0000000..51c6074 --- /dev/null +++ b/factories/redis-sqlite/src/main/java/org/proto4j/redis/sqlite/SQLiteSource.java @@ -0,0 +1,89 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sqlite; //@date 03.09.2022 + +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sql.SQLPrincipal; +import org.proto4j.redis.sql.SQLSource; + +import java.security.Principal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Objects; + +// same as DefaultSQLSource +public class SQLiteSource extends SQLSource { + + private volatile Connection connection; + + protected SQLiteSource(SQLConfiguration configuration) { + super(configuration); + } + + @Override + public Connection getConnection() throws SQLException { + if (connection != null) { + return connection; + } + + String url = getConfiguration().getQualifiedName(); + Objects.requireNonNull(url); + + if (getConfiguration().getPrincipal() != null) { + Principal principal = getConfiguration().getPrincipal(); + if (principal instanceof SQLPrincipal) { + SQLPrincipal up = ((SQLPrincipal) principal); + connection = DriverManager.getConnection(url, principal.getName(), new String(up.getPassword())); + up.destroy(); + } + } else { + if (getConfiguration().getProperties() != null) { + connection = DriverManager.getConnection(url, getConfiguration().getProperties()); + } else { + connection = DriverManager.getConnection(url); + } + } + return connection; + } + + @Override + public PreparedStatement prepare(String sql) throws SQLException { + Connection c = getConnection(); + if (connection == null) { + c = getConnection(); + } + if (c == null || sql == null || sql.isEmpty()) { + throw new SQLException("Statement is null"); + } + return c.prepareStatement(sql); + } + + @Override + public boolean isConnected() { + return connection != null; + } +} diff --git a/factories/redis-sqlite/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory b/factories/redis-sqlite/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory new file mode 100644 index 0000000..433d3b1 --- /dev/null +++ b/factories/redis-sqlite/src/main/resources/META-INF/services/org.proto4j.redis.sql.SQLFactory @@ -0,0 +1,25 @@ +# +# MIT License +# +# Copyright (c) 2022 Proto4j +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +org.proto4j.redis.sqlite.SQLiteFactory \ No newline at end of file diff --git a/factories/redis-sqlite/src/main/resources/redis-sqlite.properties b/factories/redis-sqlite/src/main/resources/redis-sqlite.properties new file mode 100644 index 0000000..ec465a3 --- /dev/null +++ b/factories/redis-sqlite/src/main/resources/redis-sqlite.properties @@ -0,0 +1,2 @@ +name=Redis SQLite +version=1.10.2 \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..8656880 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * A small and simple module definition that makes it possible to use the + * {@link org.proto4j.redis.sql.SQLFactory} class as a service. Please take + * a look at the {@link org.proto4j.redis.FactoryManager} class to get + * more information on how to integrate other factories. + * + * @see org.proto4j.redis.sql.SQLFactory + * @see org.proto4j.redis.FactoryManager + */ +module proto4j.redis { + // this module builds an API on top of the standard Java SQL-API. + requires java.sql; + + exports org.proto4j.redis; + exports org.proto4j.redis.sql; + + uses org.proto4j.redis.sql.SQLFactory; +} \ No newline at end of file diff --git a/src/main/java/org/proto4j/redis/Extractors.java b/src/main/java/org/proto4j/redis/Extractors.java new file mode 100644 index 0000000..0aeba48 --- /dev/null +++ b/src/main/java/org/proto4j/redis/Extractors.java @@ -0,0 +1,293 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis; //@date 31.08.2022 + +import org.proto4j.redis.sql.SQL; +import org.proto4j.redis.sql.SQLContext; +import org.proto4j.redis.sql.SQLExtractor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A utility class used to register new {@link SQLExtractor} instances and + * get a defined one from the given type. + * + * @see RedisExtractor + * @since 1.0 + */ +public final class Extractors { + + /** + * A list of declared {@link RedisExtractor} instances + */ + private static final List> EXTRACTORS = new LinkedList<>(); + + static { + // the default extractors are added by default + registerExtractor(new PrimitiveExtractor()); + registerExtractor(new InstanceCreator<>(null)); + registerExtractor(new RawExtractor()); + registerExtractor(new ListExtractor()); + } + + // No instance(s) of this class should be defined externally + private Extractors() {} + + /** + * Returns the defined {@link RedisExtractor} for the given parameterized + * type. + * + * @param cls the base type with a generic type declaration + * @param genericType a parameterized type instance + * @return {@code null} if no {@link RedisExtractor} instance could handle + * the base type, otherwise the corresponding extractor instance. + * @see #getDefinedExtractor(Class) + */ + public static RedisExtractor getDefinedExtractor(Class cls, Type genericType) { + Objects.requireNonNull(cls); + + synchronized (EXTRACTORS) { + for (RedisExtractor extractor : EXTRACTORS) { + if (extractor.accept(cls)) { + if (extractor instanceof ListExtractor) { + return new ListExtractor(cls, genericType); + } + return extractor.newInstance(cls); + } + } + } + return null; + } + + /** + * Returns the defined {@link RedisExtractor} for the given base type. + * + * @param cls the base type with a generic type declaration + * @return {@code null} if no {@link RedisExtractor} instance could handle + * the base type, otherwise the corresponding extractor instance. + */ + public static RedisExtractor getDefinedExtractor(Class cls) { + return getDefinedExtractor(cls, null); + } + + /** + * Registers the given extractor the default ones. + * + * @param extractor The new extractor instance. + */ + public static void registerExtractor(RedisExtractor extractor) { + Objects.requireNonNull(extractor); + + synchronized (EXTRACTORS) { + EXTRACTORS.add(extractor); + } + } + + static String getSqlFromAnnotation(Method method, AtomicReference declaredAnnotation) { + Objects.requireNonNull(method); + + Annotation[] annotations = method.getDeclaredAnnotations(); + if (annotations.length == 0) { + return null; + } + + String sql = null; + try { + for (Annotation a : annotations) { + if (a.annotationType().getDeclaringClass() == SQL.class) { + Object value = a.annotationType().getDeclaredMethod("value") + .invoke(a); + sql = value.toString(); + + if (SQL.Environment.isDefined(sql)) { + Object key = a.annotationType().getDeclaredMethod("property") + .invoke(a); + sql = SQL.Environment.getProperty(key.toString()); + } + declaredAnnotation.set(a); + break; + } + } + } catch (ReflectiveOperationException e) { + return null; + } + + return sql; + } + + public static class PrimitiveExtractor implements RedisExtractor { + + public static boolean isPrimitive(Class cls) { + if (cls == null || isChar(cls)) return false; + + if (cls.isPrimitive() || cls == String.class) return true; + + try { + Field type = cls.getDeclaredField("TYPE"); + return true; + + } catch (NoSuchFieldException e) { + return false; + } + } + + private static boolean isChar(Class cls) { + return cls == char.class || cls == Character.class; + } + + @Override + public Object read(ResultSet resultSet, SQLContext context) throws SQLException { + if (context.getReferenceParent() != null) { + Class parent = context.getReferenceParent(); + + if (parent == String.class) { + return resultSet.getString((String) context.getReference()); + } + try { + String typename = parent.getName(); + if (!parent.isPrimitive()) { + Field type = parent.getDeclaredField("TYPE"); + + Class primitiveType = (Class) type.get(null); // static + typename = primitiveType.getSimpleName(); + } + + String name = "get" + typename.toUpperCase().charAt(0) + typename.substring(1); + + // assert method is defined + Method method = resultSet.getClass().getMethod(name, String.class); + return method.invoke(resultSet, context.getReference().toString()); + + } catch (ReflectiveOperationException e) { + throw new SQLException(e); + } + } + throw new IllegalArgumentException(); + } + + @Override + public boolean accept(Class cls) { + return isPrimitive(cls); + } + + @Override + public RedisExtractor newInstance(Class cls) { + //noinspection unchecked + return (RedisExtractor) this; + } + } + + public static class RawExtractor implements RedisExtractor { + + @Override + public boolean accept(Class cls) { + return ResultSet.class.isAssignableFrom(cls); + } + + @Override + public RedisExtractor newInstance(Class cls) { + //noinspection unchecked + return (RedisExtractor) new RawExtractor(); + } + + @Override + public ResultSet read(ResultSet resultSet, SQLContext context) throws SQLException { + return resultSet; + } + } + + public static class ListExtractor implements RedisExtractor> { + + private final Class type; + + private RedisExtractor extractor; + + public ListExtractor() { + this(null, null); + } + + public ListExtractor(Class type, Type genericType) { + this.type = type; + this.extractor = null; + if (genericType != null) { + if (genericType instanceof ParameterizedType) { + Type[] types = ((ParameterizedType) genericType) + .getActualTypeArguments(); + if (types.length == 0) { + throw new IllegalArgumentException("CRITICAL: class is not typed"); + } + + Class cls = (Class) types[0]; + this.extractor = getDefinedExtractor(cls); + if (extractor == null) { + throw new IllegalArgumentException( + "No extractor for type '" + cls.getSimpleName() + "' defined" + ); + } + } + } + } + + + @Override + public boolean accept(Class cls) { + return List.class.isAssignableFrom(cls); + } + + @Override + public RedisExtractor newInstance(Class cls) { + //noinspection unchecked + return (RedisExtractor) new ListExtractor(cls, null); + } + + @Override + public List read(ResultSet resultSet, SQLContext context) throws SQLException { + Class type = this.type; + if (type == null) return Collections.emptyList(); + + if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { + type = ArrayList.class; + } + + try { + //noinspection unchecked + List list = (List) type.getDeclaredConstructor().newInstance(); + + while (resultSet.next()) { + Object o = extractor.read(resultSet, context); + list.add(o); + } + return list; + } catch (ReflectiveOperationException e) { + throw new SQLException("could not create list instance"); + } + } + } +} diff --git a/src/main/java/org/proto4j/redis/FactoryManager.java b/src/main/java/org/proto4j/redis/FactoryManager.java new file mode 100644 index 0000000..222f808 --- /dev/null +++ b/src/main/java/org/proto4j/redis/FactoryManager.java @@ -0,0 +1,220 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis; //@date 02.09.2022 + +import org.proto4j.redis.sql.SQLFactory; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Stream; + +/** + * The basic service for managing a set of SQLFactories. + *

+ * As part of its initialization, the {@code FactoryManager} class will + * attempt to load available {@code SQLFactory} classes by using: + *

    + *
  • Service providers of the {@code java.sql.Driver} class, that are loaded + * via the {@link ServiceLoader#load(Class)} mechanism.
  • + *
+ * + * @apiNote {@code FactoryManager} initialization is done lazily and looks up + * service providers using the thread context class loader. The drivers loaded + * and available to an application will depend on the thread context class + * loader of the thread that triggers driver initialization by + * {@code FactoryManager}. + * + * @see SQLFactory + * @since 1.0 + */ +public class FactoryManager { + + // list of registered SQLFactory classes + private static final CopyOnWriteArrayList registeredFactories = + new CopyOnWriteArrayList<>(); + + // used to synchronize all callers that access the registered factories + private static final Object FACTORY_LOCK = new Object(); + + // used to indicate whether factories were initialized once + private static volatile boolean factoriesLoaded; + + // Prevents the FactoryManager class from being instantiated. + private FactoryManager() {} + + /** + * Registers the given factory with the {@code FactoryManager}. + * A newly-loaded driver class should call the method {@code registerDriver} + * to make itself known to the {@code FactoryManager}. If the driver is currently + * registered, no action is taken. + * + * @param factory the new {@link SQLFactory} that is to be registered with the + * {@code FactoryManager} + * @throws NullPointerException if {@code factory} is null + */ + public static void registerFactory(SQLFactory factory) { + synchronized (FACTORY_LOCK) { + Objects.requireNonNull(factory); + + registeredFactories.addIfAbsent(new FactoryInfo(factory)); + } + } + + /** + * Removes the specified driver from the {@code FactoryManager}'s list of + * registered factories. + *

+ * If a {@code null} value is specified for the driver to be removed, then no + * action is taken. + *

+ * If the specified factory is not found in the list of registered factories, + * then no action is taken. If the factory was found, it will be removed + * from the list of registered factories. + * + * @param factory the {@link SQLFactory} to remove + */ + public static void deregisterFactory(SQLFactory factory) { + if (factory == null) { + return; + } + + synchronized (FACTORY_LOCK) { + FactoryInfo info = new FactoryInfo(factory); + + registeredFactories.remove(info); + } + } + + /** + * Attempts to locate a factory that is bound to the given driver type. + * + * @param driverType a string representation of the driver type + * @return a {@link SQLFactory} object that can create {@code SQLService} + * and {@code SQLSource} instances. + */ + public static SQLFactory getFactory(String driverType) { + Objects.requireNonNull(driverType); + + ensureDriversInitialized(); + + synchronized (FACTORY_LOCK) { + for (FactoryInfo info : registeredFactories) { + if (info.factory.engineDriverType() + .equalsIgnoreCase(driverType)) { + return info.factory; + } + } + } + + throw new IllegalArgumentException("No suitable factory found for " + driverType); + } + + /** + * Retrieves an Enumeration with all the currently loaded factories + * to which the current caller has access. + * + *

Note: The classname of a driver can be found using + * d.getClass().getName() + * + * @return the list of {@link SQLFactory} loaded by the caller's class loader + * @see #factories() + */ + public static Enumeration getFactories() { + return Collections.enumeration(getFactoriesInternal()); + } + + /** + * Retrieves a Stream with all the currently loaded factories + * to which the current caller has access. + * + * @return the stream of {@link SQLFactory} loaded by the caller's + * class loader + * @since Java 9 + */ + public static Stream factories() { + return getFactoriesInternal().stream(); + } + + private static List getFactoriesInternal() { + List result = new LinkedList<>(); + + for (FactoryInfo info : registeredFactories) { + result.add(info.factory); + } + return result; + } + + private static void ensureDriversInitialized() { + if (factoriesLoaded) { + return; + } + + synchronized (FACTORY_LOCK) { + if (factoriesLoaded) { + return; + } + + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + ServiceLoader factories = ServiceLoader.load(SQLFactory.class); + Iterator iterator = factories.iterator(); + + try { + while (iterator.hasNext()) { + SQLFactory factory = iterator.next(); + } + } catch (Throwable t) { + // ignore that exception + } + return null; + } + }); + + factoriesLoaded = true; + } + } + + private static class FactoryInfo { + + final SQLFactory factory; + + private FactoryInfo(SQLFactory factory) {this.factory = factory;} + + @Override + public boolean equals(Object obj) { + return obj instanceof FactoryInfo + && this.factory == ((FactoryInfo) obj).factory; + } + + @Override + public int hashCode() { + return factory.hashCode(); + } + } + +} diff --git a/src/main/java/org/proto4j/redis/InstanceCreator.java b/src/main/java/org/proto4j/redis/InstanceCreator.java new file mode 100644 index 0000000..d071915 --- /dev/null +++ b/src/main/java/org/proto4j/redis/InstanceCreator.java @@ -0,0 +1,149 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis; //@date 31.08.2022 + +import org.proto4j.redis.sql.Entity; +import org.proto4j.redis.sql.SQL; +import org.proto4j.redis.sql.SQLContext; +import org.proto4j.redis.sql.SQLExtractor; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +final class InstanceCreator implements RedisExtractor { + + private final Class type; + + private Constructor constructor; + + InstanceCreator(Class type) { + this.type = type; + if (type != null) { + collect(); + } + } + + private final ConcurrentMap columns = new ConcurrentHashMap<>(); + + private void collect() { + if (!type.isAnnotationPresent(Entity.class)) { + throw new IllegalArgumentException("Type is not an Entity.class"); + } + + if (!columns.isEmpty()) { + throw new IllegalCallerException("This method should be called only once!"); + } + + for (Field field : type.getDeclaredFields()) { + SQL.Column column = field.getDeclaredAnnotation(SQL.Column.class); + if (column != null) { + String name = column.value(); + if (name.isEmpty()) { + name = field.getName(); + } + columns.putIfAbsent(name, field); + } + } + + try { + constructor = type.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + public final T internalInstance() { + try { + return constructor.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } catch (NullPointerException f) { + return null; + } + } + + @Override + public RedisExtractor newInstance(Class cls) { + return new InstanceCreator<>(cls); + } + + @Override + public T read(ResultSet resultSet, SQLContext context) throws SQLException { + if (resultSet.isClosed()) { + throw new SQLException("ResultSet was closed"); + } + + T value = internalInstance(); + if (value == null) { + throw new SQLException("Could not create entity instance"); + } + + Object ref = context.getReference(); + Class parent = context.getReferenceParent(); + + // We are using this approach to fill up all fields that are stored + // as a column in the ResultSet object. In order to make it possible + // to fetch partial object data, this approach suits very well. + for (int i = 1; i < resultSet.getMetaData().getColumnCount(); i++) { + String column = resultSet.getMetaData().getColumnName(i); + if (!columns.containsKey(column)) { + continue; + } + + Field field = columns.get(column); + context.setReference(column); + context.setReferenceParent(field.getType()); + + SQLExtractor extractor = Extractors.getDefinedExtractor(field.getType()); + if (extractor != null) { + Object v = extractor.read(resultSet, context); + field.setAccessible(true); + try { + field.set(value, v); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + context.setReference(ref); + context.setReferenceParent(parent); + return value; + } + + @Override + public boolean accept(Class cls) { + return isTyped(cls); + } + + public static boolean isTyped(Class cls) { + return cls.isAnnotationPresent(Entity.class); + } +} diff --git a/src/main/java/org/proto4j/redis/MethodBuilder.java b/src/main/java/org/proto4j/redis/MethodBuilder.java new file mode 100644 index 0000000..7bdb49e --- /dev/null +++ b/src/main/java/org/proto4j/redis/MethodBuilder.java @@ -0,0 +1,142 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis; //@date 01.09.2022 + +import org.proto4j.redis.sql.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +class MethodBuilder { + + private final SQLMethod method = new SQLMethod(); + + private final List info = new LinkedList<>(); + + public static MethodBuilder newBuilder() { + return new MethodBuilder(); + } + + public static SQLMethod read(Method method, SQLService service, SQLValidator validator) { + MethodBuilder builder = newBuilder().verifier(validator); + + AtomicReference used = new AtomicReference<>(null); + builder.setStatement(Extractors.getSqlFromAnnotation(method, used)); + for (Parameter parameter : method.getParameters()) { + if (!parameter.isAnnotationPresent(Param.class)) { + continue; + } + Param param = parameter.getDeclaredAnnotation(Param.class); + + SQLMethod.ParamInfo paramInfo = new SQLMethod.ParamInfo(); + paramInfo.array = param.value().equals(SQL.ARRAY) + || parameter.getType().isArray(); + paramInfo.mapped = param.value().equals(SQL.MAP) + || Map.class.isAssignableFrom(parameter.getType()); + + paramInfo.typed = InstanceCreator.isTyped(parameter.getType()); + + paramInfo.name = param.value().isEmpty() + ? parameter.getName() : param.value(); + + builder.add(paramInfo); + } + + String qMethod = used.get().annotationType() + .getSimpleName().toLowerCase(); + Method worker = null; + // Because the select()-method contains two arguments the secure + // and concurrent way to retrieve the worker method is used. + for (Method m0 : service.getClass().getDeclaredMethods()) { + if (m0.getName().equals(qMethod)) { + worker = m0; + break; + } + } + + if (worker == null) { + throw new NullPointerException("Could not find service method: " + qMethod); + } + + SQLMethod.Worker w; + if (qMethod.equals(SQL.Select.class.getSimpleName().toLowerCase())) { + SQLExtractor e = Extractors.getDefinedExtractor(method.getReturnType(), method.getGenericReturnType()); + if (e == null) { + throw new IllegalArgumentException("Return type not defined"); + } + + w = (stmt) -> service.select(stmt, e); + } + else { + Method finalWorker = worker; + w = (sql) -> invokeWorker(service, finalWorker, sql); + } + + return builder.workWith(w).finish(); + } + + private static Object invokeWorker(SQLService service, Method worker, String sql) { + try { + return worker.invoke(service, sql); + } catch (IllegalAccessException | InvocationTargetException e) { + return null; + } + } + + public MethodBuilder verifier(SQLValidator validator) { + method.setValidator(validator); + return this; + } + + public MethodBuilder setStatement(String sql) { + Objects.requireNonNull(sql); + method.setSql(sql); + return this; + } + + public MethodBuilder add(SQLMethod.ParamInfo paramInfo) { + Objects.requireNonNull(paramInfo); + info.add(paramInfo); + return this; + } + + public SQLMethod finish() { + method.setParams(info.toArray(SQLMethod.ParamInfo[]::new)); + return method; + } + + public MethodBuilder workWith(SQLMethod.Worker worker) { + Objects.requireNonNull(worker); + method.setWorker(worker); + return this; + } +} diff --git a/src/main/java/org/proto4j/redis/Redis.java b/src/main/java/org/proto4j/redis/Redis.java new file mode 100644 index 0000000..6db2873 --- /dev/null +++ b/src/main/java/org/proto4j/redis/Redis.java @@ -0,0 +1,204 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis; //@date 01.09.2022 + +import org.proto4j.redis.sql.*; + +import java.lang.reflect.*; +import java.sql.SQLException; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Redis adapts a Java interface into calls to a connected database by using + * annotations on the declared methods and its parameters. Create instances + * with providing your interface class and the {@link SQLConfiguration} or + * {@link SQLSource} to the {@link #create} method. + *

+ * For example (with usage of the SQLite factory), + *


+ * SQLConfiguration config = new SQLiteConfiguration("mydb");
+ * MyApi api = Redis.create(MyApi.class, config);
+ *
+ * List<User> list = api.fetchUsersFrom("table_two");
+ * 
+ * + * @author MatrixEditor + * @version 1.0 + */ +public final class Redis { + + // no instance creation allowed + private Redis() {} + + /** + * Create an implementation of the API methods defined by the {@code api} + * interface. + *

+ * Method parameters annotated with {@link Param} can be used to dynamically + * replace parts of the sql statement. Replacement sections are denoted by + * an identifier surrounded by curly braces (e.g., "{abc}"). + *

+ * The creation of {@link SQLSource} and {@link SQLService} objects are + * delegated to the specified class that implements the basic functions + * from the {@link SQLFactory} interface. + *

+ * For example, + *

+     * @SQL(SQLiteFactory.FACTORY)
+     * public interface UserStorage {
+     *     @SQL.Select("select * from {table}")
+     *     public List<User> fetchUsersFrom(@Param("table") String tb);
+     * }
+     * 
+ * + * @param cls the interface tha will be implemented + * @param configuration the {@link SQLConfiguration} instance used to + * create a {@link SQLSource}. + * @param the type of the api + * @return a new instance for the specified interface type + */ + public static API create(Class cls, SQLConfiguration configuration) { + validateClass(cls, configuration); + + SQLService service = buildService(cls, configuration); + return getApiService(cls, service); + } + + /** + * Create an implementation of the API methods defined by the {@code api} + * interface. + * + * @param cls the interface tha will be implemented + * @param source the {@link SQLSource} instance with a {@link SQLConfiguration}. + * @param the type of the api + * @see #create(Class, SQLConfiguration) + * @return a new instance for the specified interface type + */ + public static API create(Class cls, SQLSource source) { + validateClass(cls, source); + + SQLService service = buildService(cls, source); + return getApiService(cls, service); + } + + private static void validateClass(Class cls, Object source) { + Objects.requireNonNull(cls); + Objects.requireNonNull(source); + + if (!cls.isInterface()) { + throw new IllegalArgumentException("API type is not an interface"); + } + } + + private static API getApiService(Class cls, SQLService service) { + SQLValidator validator = buildValidator(cls); + + InvocationHandler handler = buildHandler(service, validator); + //noinspection unchecked + return (API) Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, handler); + } + + private static InvocationHandler buildHandler(SQLService service, SQLValidator validator) { + // put methods into map with name identifier + return new InvocationHandler() { + private final ConcurrentMap methodCache = new ConcurrentHashMap<>(); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (method.getDeclaringClass() == Object.class) { + // A common mistake is to invoke this method with the proxy + // object given as an argument value. If the 'proxy' object + // is used to invoke the method it will loop infinitely, + // because the proxy object is referring to the InvocationHandler + // object. 'this' will be the equivalent to the used + // class instance. + return method.invoke(this, args); + } + + SQLMethod apiMethod = methodCache.get(method); + if (apiMethod == null) { + apiMethod = MethodBuilder.read(method, service, validator); + methodCache.put(method, apiMethod); + } + + return apiMethod.invoke(args); + } + }; + } + + static SQLService buildService(Class cls, Object obj) { + Objects.requireNonNull(cls); + Objects.requireNonNull(obj); + + if (!cls.isAnnotationPresent(SQL.class)) { + throw new IllegalArgumentException("SQLService not defined!"); + } + + try { + String driverType = + cls.getDeclaredAnnotation(SQL.class).value(); + + SQLFactory factory = FactoryManager.getFactory(driverType); + if (obj instanceof SQLSource) { + return factory.engineGetService((SQLSource) obj); + } else if (obj instanceof SQLConfiguration) { + SQLSource source = + factory.engineGetSource((SQLConfiguration) obj); + + return factory.engineGetService(source); + } + } catch (SQLException e) { + throw new IllegalArgumentException("SQLService.(SQLSource.class) not called"); + } + throw new IllegalArgumentException("Could not build SQLService"); + } + + static SQLValidator buildValidator(Class cls) { + Objects.requireNonNull(cls); + + if (!cls.isAnnotationPresent(Validator.class)) { + return null; + } + + try { + Class type = + cls.getDeclaredAnnotation(Validator.class).value(); + + Constructor constructor = + type.getDeclaredConstructor(); + + return constructor.newInstance(); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("SQLValidator.() not defined!"); + + } catch (InvocationTargetException | InstantiationException + | IllegalAccessException e) { + throw new IllegalCallerException("Could not create SQLValidator instance"); + } + } +} diff --git a/src/main/java/org/proto4j/redis/RedisExtractor.java b/src/main/java/org/proto4j/redis/RedisExtractor.java new file mode 100644 index 0000000..8319dc1 --- /dev/null +++ b/src/main/java/org/proto4j/redis/RedisExtractor.java @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis;//@date 31.08.2022 + +import org.proto4j.redis.sql.SQLExtractor; + +/** + * A specific type of extractor used by the utility class {@link Extractors} + * to filter registered extractors and create new instances based on the + * given type; + * + * @param the type that should be extracted + */ +public interface RedisExtractor extends SQLExtractor { + + /** + * Retrieves whether this extractor can handle the given type. + * + * @param cls a class instance specifying the type that should be + * extracted. + * @return {@code true} if accepted, otherwise {@code false} + */ + public boolean accept(Class cls); + + /** + * Creates a new extractor based on the given type. + * + * @param cls a class instance specifying the type that should be + * extracted. + * @param the next return type + * @return a new RedisExtractor instance for the given type + */ + public RedisExtractor newInstance(Class cls); +} diff --git a/src/main/java/org/proto4j/redis/SQLMethod.java b/src/main/java/org/proto4j/redis/SQLMethod.java new file mode 100644 index 0000000..995c44f --- /dev/null +++ b/src/main/java/org/proto4j/redis/SQLMethod.java @@ -0,0 +1,158 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis; //@date 31.08.2022 + +import org.proto4j.redis.sql.SQLValidator; + +import java.lang.reflect.Array; +import java.sql.SQLException; +import java.util.Map; + +class SQLMethod { + + private Worker worker; + private SQLValidator validator; + private String sql; + private ParamInfo[] params; + + public static SQLMethod ofNullable() { + return new SQLMethod() { + @Override + public Object invoke(Object[] args) throws SQLException { + return null; + } + }; + } + + public Object invoke(Object[] args) throws SQLException { + // apply parameters to sql + // verify sql + // execute the worker + if (params.length != args.length) { + throw new SQLException("Invalid parameter length: must be " + params.length); + } + + String statement = sql; + for (int i = 0; i < params.length; i++) { + String nmParam = '{' + params[i].name + '}'; + if (!sql.contains(nmParam)) { + throw new SQLException("Parameter pattern not found: " + nmParam); + } + + Object value = args[i]; + String replacement = value.toString(); + if (!(value instanceof String) && !(value instanceof Character)) { + if (params[i].array) { + replacement = fromArray(value); + } + else if (params[i].typed) { + throw new UnsupportedOperationException("Typed parameters are not allowed"); + } + else if (params[i].mapped) { + statement = fromMap(value); + continue; + } + } else { + replacement = "'" + replacement + "'"; + } + statement = statement.replace(nmParam, replacement); + } + + if (getValidator() != null) { + if (!getValidator().verify(statement)) { + throw new SQLException("Could not verify SQL statement"); + } + } + + return worker.invoke(statement); + } + + private String fromArray(Object value) { + StringBuilder sb = new StringBuilder(", "); + + for (int j = 0; j < Array.getLength(value); j++) { + Object next = Array.get(value, j); + + String rp = next.toString(); + if (next instanceof String || next instanceof Character) { + rp = "'" + rp + "'"; + } + sb.append(rp); + } + return sb.toString(); + } + + private String fromMap(Object value) { + Map map = (Map) value; + String st = sql; + + for (Object key : map.keySet()) { + String pattern = '{' + key.toString() + '}'; + if (st.contains(pattern)) { + Object mappedValue = map.get(key); + if (mappedValue instanceof String + || mappedValue instanceof Character) { + st = st.replace(pattern, "'" + mappedValue.toString() + "'"); + } + else { + st = st.replace(pattern, String.valueOf(mappedValue)); + } + } + } + return st; + } + + public SQLValidator getValidator() { + return validator; + } + + public void setValidator(SQLValidator validator) { + this.validator = validator; + } + + public void setParams(ParamInfo[] params) { + this.params = params; + } + + public void setWorker(Worker worker) { + this.worker = worker; + } + + public void setSql(String sql) { + this.sql = sql; + } + + public static interface Worker { + public Object invoke(String sql) throws SQLException; + } + + public static class ParamInfo { + boolean typed; + boolean mapped; + boolean array; + + String name; + } +} diff --git a/src/main/java/org/proto4j/redis/package-info.java b/src/main/java/org/proto4j/redis/package-info.java new file mode 100644 index 0000000..3f59ca2 --- /dev/null +++ b/src/main/java/org/proto4j/redis/package-info.java @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Proto4j-Redis turn your database API from a Java interface into a usable + * implementation. + * + *
+ * @SQL(SQLiteFactory.FACTORY)
+ * public interface UserStorage {
+ *     @SQL.Select("select * from {table}")
+ *     public List<User> fetchUsersFrom(@Param("table") String tb);
+ * }
+ * 
+ * + * @author MatrixEditor + * @author Proto4j + * @version 1.0 + **/ +package org.proto4j.redis; \ No newline at end of file diff --git a/src/main/java/org/proto4j/redis/sql/Entity.java b/src/main/java/org/proto4j/redis/sql/Entity.java new file mode 100644 index 0000000..b761d15 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/Entity.java @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +import java.lang.annotation.*; + +/** + * Indicates that the annotated class is typed and will be extracted using an + * {@code InstanceCreator} while values are fetched from a database. + * + * @since 1.0 + * @author MatrixEditor + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Entity { +} diff --git a/src/main/java/org/proto4j/redis/sql/Param.java b/src/main/java/org/proto4j/redis/sql/Param.java new file mode 100644 index 0000000..3362289 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/Param.java @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +import java.lang.annotation.*; + +/** + * Used to set a specific name to the annotated parameter. This declaration + * sets the column name to a parameter within a method. + *

+ * The following example shows how to use this annotation on a parameter. It + * is strongly recommended to use this annotation for declaring column names, + * because the parameter names starting from {@code arg0} are used otherwise. + *

+ * Arguments from methods that are annotated with {@link Param} can be + * used to change the statement at runtime. The provided name has to be + * present in the sql statement: + *

+ *      PATTERN := '{' NAME '}';
+ * 
+ * Based on that pattern definition the sql statement can be defined as + * follows: + *
+ *      @SQL.Select("select * from {table}")
+ *      //..
+ * 
+ * + * @since 1.0 + * @author MatrixEditor + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Param { + /** + * @return the specified column name. + */ + String value() default ""; +} diff --git a/src/main/java/org/proto4j/redis/sql/SQL.java b/src/main/java/org/proto4j/redis/sql/SQL.java new file mode 100644 index 0000000..50459fa --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQL.java @@ -0,0 +1,284 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +import java.lang.annotation.*; +import java.util.Objects; +import java.util.Properties; + +/** + * Sets the used {@link SQLFactory} and {@link java.sql.Driver} by providing + * its identifier. + * + * @apiNote It is recommended to use the SQL.Environment class to save the + * used sql statement templates temporary. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SQL { + + /** + * Used within a {@link Param} annotation it can be used to indicate the + * sql statement is saved in the SQL.Environment class. + */ + public static final String ENV = "$sql:env"; + + /** + * Indicates the annotated parameter stores its values in a {@link java.util.Map}. + */ + public static final String MAP = "$sql:type.map"; + + /** + * Indicates all parameters declared in the sql statement will be replaced + * step by step while iterating over the given array. + */ + public static final String ARRAY = "$sql:type.array"; + + /** + * @return the jdbc driver type name. + */ + String value(); + + /** + * Indicates the annotated method should execute a SQL-Select statement. + * The result depends on the given return type and can be wrapped into + * an {@link Entity} annotated type. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Select { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Indicates the annotated method should execute a SQL-Update statement. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Update { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Indicates the annotated method should execute a SQL-Insert statement. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Insert { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Indicates the annotated method should execute a SQL-Delete statement. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Delete { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Indicates the annotated method should execute a SQL-Create statement. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Create { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Indicates the annotated method should execute a SQL-Drop statement. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Drop { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Indicates the annotated method should execute a SQL-Raw statement. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Raw { + /** + * @return the sql statement with argument patterns + */ + String value(); + + /** + * @return the property descriptor if an environment statement should + * be used. + */ + String property() default ""; + } + + /** + * Fields annotated with the {@link Column} annotation will be used by the + * {@code InstanceCreator} to wrap data in a {@link java.sql.ResultSet} into + * a new type instance. + *

+ * The name of column provided here has to be defined in the database, + * otherwise an {@link java.sql.SQLException} will be thrown. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Column { + /** + * @return the name of the corresponding column in the database. + */ + String value(); + } + + /** + * The SQL.Environment class can be used to store SQL statements globally + * for a specific time period. It can be used within the {@link #ENV} + * variable in a {@link Param} annotation. + * + * @since 1.0 + */ + public static class Environment { + + /** + * The provided properties with all stored template statements. + */ + private static volatile Properties properties; + + /** + * Returns whether the given input string equals the {@link #ENV} + * indicator. + * + * @param s the input string + * @return s == {@link #ENV} (content equals) + */ + public static boolean isDefined(String s) { + return s != null && s.equals(ENV); + } + + /** + * Sets up a new environment with the given properties instance. + * + * @param p the new properties + */ + public static synchronized void setupEnvironment(Properties p) { + Objects.requireNonNull(p); + properties = p; + } + + /** + * Retrieves a stored property for the given name. + * + * @param name the name of the stored property + * @return the mapped property value + */ + public static String getProperty(String name) { + Objects.requireNonNull(name); + return properties.getProperty(name); + } + + /** + * Resets the current environment. + */ + public static synchronized void clear() { + if (properties != null) { + properties.clear(); + properties = null; + } + } + } + + +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLConfiguration.java b/src/main/java/org/proto4j/redis/sql/SQLConfiguration.java new file mode 100644 index 0000000..c94d2a0 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLConfiguration.java @@ -0,0 +1,135 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 29.08.2022 + +import java.security.Principal; +import java.util.Properties; + +/** + * The base class for all SQLConfiguration objects. This class is used to provide + * a set of information about the connection that will be established. + *

+ * The connection url will be provided through {@link #getQualifiedName()}, which + * is implemented in every inheritor of this class. + * + * @since 1.0 + */ +public abstract class SQLConfiguration { + + /** + * The jdbc.driver name. + */ + private final String driverType; + + /** + * Additional properties as an alternative to the {@link SQLPrincipal} for + * authentication. + */ + private Properties properties; + + /** + * Used for database authentication. + */ + private Principal principal; + + /** + * Create a new configuration instance without any additional configuration. + * + * @param driverType the jdbc driver name + */ + public SQLConfiguration(String driverType) { + this(driverType, null); + } + + /** + * Create a new configuration instance with additional configuration + * properties. These could also include the username and password + * property for authentication. + * + * @param driverType the jdbc driver name + * @param properties the configuration properties + */ + public SQLConfiguration(String driverType, Properties properties) { + this(driverType, properties, null); + } + + + /** + * A utility constructor for creating overloaded constructors. + * + * @param driverType the jdbc driver name + * @param properties the configuration properties + * @param principal the authentication parameters + */ + protected SQLConfiguration(String driverType, Properties properties, SQLPrincipal principal) { + this.driverType = driverType; + this.properties = properties; + this.principal = principal; + } + + /** + * @return the database connection url + */ + public abstract String getQualifiedName(); + + /** + * @return the jdbc driver name + */ + public String getDriverType() { + return driverType; + } + + /** + * @return the authentication parameters + */ + public Principal getPrincipal() { + return principal; + } + + /** + * @return the configuration properties + */ + public Properties getProperties() { + return properties; + } + + /** + * Sets new authentication parameters with the dedicated {@link SQLPrincipal}. + * + * @param principal the authentication parameters + */ + public void setPrincipal(SQLPrincipal principal) { + this.principal = principal; + } + + /** + * Applies new properties to this configuration instance. + * + * @param properties the configuration properties + */ + public void setProperties(Properties properties) { + this.properties = properties; + } +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLContext.java b/src/main/java/org/proto4j/redis/sql/SQLContext.java new file mode 100644 index 0000000..c1328d9 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLContext.java @@ -0,0 +1,70 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +/** + * The base class for objects that implement the behaviour of an SQLContext. This + * class is used within the instance creation process and should not be used + * at any other place. + * + * @see SQLExtractor + * @author MatrixEditor + */ +public interface SQLContext { + + /** + * @return the current data source + */ + SQLSource getSource(); + + /** + * @return the declaring class of the called API method. + */ + Class getAPIServiceType(); + + /** + * @return a wrapper object for the top level reference. + */ + Object getReference(); + + /** + * Sets the reference to the given value. + * + * @param reference a wrapper object for the top level reference + */ + void setReference(Object reference); + + /** + * @return the declaring class of {@link #getReference()}. + */ + Class getReferenceParent(); + + /** + * Sets the declaring class of {@link #getReference()}. + * + * @param cls the defined class instance + */ + void setReferenceParent(Class cls); +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLExtractor.java b/src/main/java/org/proto4j/redis/sql/SQLExtractor.java new file mode 100644 index 0000000..420bbd2 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLExtractor.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Represents a function that reads data from the given {@link ResultSet} with + * a {@link SQLContext} in mind and produces a result. + * + * @param the type of the result of the extractor. + */ +@FunctionalInterface +public interface SQLExtractor { + + /** + * Applies this extractor to the given {@link ResultSet} and {@link SQLContext}. + * + * @param resultSet the provided query result + * @param context an object covering context variables + * @return an instance of the result type + * @throws SQLException if an I/O error occurs + */ + public T read(ResultSet resultSet, SQLContext context) throws SQLException; +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLFactory.java b/src/main/java/org/proto4j/redis/sql/SQLFactory.java new file mode 100644 index 0000000..ae04537 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLFactory.java @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql; //@date 02.09.2022 + +import java.sql.SQLDataException; +import java.sql.SQLException; +import java.sql.SQLWarning; + +/** + * The base class every external factory must implement. This framework allows + * multiple factories. + *

+ * Each factory should apply a class that implements the SQLFactory interface. The + * {@code FactoryManager} will try to load as many drivers as it can find and then + * will be queried by the {@link org.proto4j.redis.Redis} class in order to + * retrieve the right factory for creating the SQLService instance. + *

+ * It is strongly recommended that each SQLFactory class should be small and + * standalone so that the SQLFactory class can be loaded and queried without + * bringing in vast quantities of supporting code. + *

+ * When a Factory class is loaded, it should create an instance of + * itself and register it with the FactoryManager. This means that a + * user can load and register a factory by following the next instructions: + *

    + *
  • {@code Class.forName("com.abc.Factory")}
  • + *
  • Creating a file named {@code META-INF/services/org.proto4j.redis.sql.SQLFactory} + * with the path to the own implementation of the SQLFactory. For example, + *
    + *     com.example.impl.MyFactory # own implementation declared in that file
    + * 
    + * For basic implementations visit the repository on the + * proto4j-redis repository + * on github. + *
  • + *
+ * + * @see org.proto4j.redis.FactoryManager + * @see org.proto4j.redis.Redis + * @since 1.0 + */ +public interface SQLFactory { + + /** + * Attempts to create a new {@link SQLSource} instance with the given + * configuration. Database connections are loaded and instantiated lazily, + * so this method call will cause no connection to a database. + * + * @param conf the given configuration instance + * @return a source object which will delegate the connecting process + * @throws SQLDataException if the configuration was incorrect + * @throws SQLWarning if any deprecation warning should be displayed + */ + public SQLSource engineGetSource(SQLConfiguration conf) + throws SQLDataException, SQLWarning; + + /** + * Retrieves a new {@link SQLService} instance for the registered factory. + * By using this method, the source already should have created a connection + * to the used database. + * + * @param source the data source + * @return a service wrapper for executing sql queries. + * @throws SQLException if an error occurs while creating the instance. + */ + public SQLService engineGetService(SQLSource source) + throws SQLException; + + /** + * Gets the corresponding driver type that is registered to the + * {@link java.sql.DriverManager}. + * + * @return the sql driver type + */ + public String engineDriverType(); + + /** + * Gets the factory's minor version number. Initially this should be 0. + * + * @return this factory's minor version number + */ + public int getMinorVersion(); + + /** + * Retrieves the factory's major version number. Initially this should + * be 1. + * + * @return this factory's major version number + */ + public int getMajorVersion(); + +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLPrincipal.java b/src/main/java/org/proto4j/redis/sql/SQLPrincipal.java new file mode 100644 index 0000000..39582ee --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLPrincipal.java @@ -0,0 +1,114 @@ +package org.proto4j.redis.sql; //@date 15.03.2022 + +import javax.security.auth.Destroyable; +import java.nio.file.attribute.UserPrincipal; +import java.util.Arrays; + +/** + * A user principal identified by a username or account name. + *

+ * This principal object should be destroyed after successful initiating a + * new database connection. In order to wipe the stored password from memory. + * + * @since 1.0 + * @see UserPrincipal + * @see Destroyable + */ +public class SQLPrincipal implements UserPrincipal, Destroyable { + + /** + * The principal's name + * + * @serial + */ + private final String name; + + /** + * The principal's password + * + * @serial + * @see #destroy() + */ + private final char[] password; + + /** + * Creates a principal. + * + * @param name The principal's string name. + * @param password The principal's password used for authentication + * @exception NullPointerException If the name is + * null. + */ + public SQLPrincipal(String name, char[] password) { + if (name == null) { + throw new NullPointerException("null name is illegal"); + } + + this.name = name; + this.password = password; + } + + /** + * Returns the name of this principal. + * + * @return The principal's name. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns the password of this principal. + * + * @return The principal's password. + */ + public char[] getPassword() { + return password; + } + + /** + * Returns a string representation of this principal. + * + * @return The principal's name. + */ + @Override + public String toString() { + return getName(); + } + + /** + * Returns a hash code for this principal. + * + * @return The principal's hash code. + */ + @Override + public int hashCode() { + return getName().hashCode(); + } + + /** + * Compares this principal to the specified object. + * + * @param obj The object to compare this principal against. + * @return true if they are equal; false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof SQLPrincipal) { + return getName().equals(((SQLPrincipal) obj).getName()); + } + return false; + } + + /** + * Destroy the password of this {@code SQLPrincipal}. + */ + @Override + public void destroy() { + Arrays.fill(password, (char) 0); + } +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLService.java b/src/main/java/org/proto4j/redis/sql/SQLService.java new file mode 100644 index 0000000..dfda3a2 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLService.java @@ -0,0 +1,137 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql; //@date 31.08.2022 + +import java.sql.SQLException; +import java.util.Objects; + +/** + * The base class for all services that execute the given sql statement on + * the connected database. This class is provided by a {@link SQLFactory} + * in order to make this framework more applicable to extensions. + * + * @since 1.0 + */ +public abstract class SQLService { + + /** + * The source storing a connection to the database. + */ + private SQLSource source; + + /** + * Creates a new SQLService with the given data source. The connection to + * the database might not be opened yet, because will be initiated lazily. + * + * @param source a source storing the database connection. + */ + public SQLService(SQLSource source) { + this.source = Objects.requireNonNull(source); + } + + /** + * Executes a SELECT-statement and wraps the returned {@link java.sql.ResultSet} + * with the given {@link SQLExtractor} to a given type. + * + * @param sql the sql statement + * @param extractor the type extractor to create the type instance + * @param the type to be extracted + * @return a new instance of type {@code T} + * @throws SQLException if an error occurs + */ + public abstract T select(String sql, SQLExtractor extractor) throws SQLException; + + /** + * Executes the given INSERT-statement. + * + * @param sql the sql statement + * @return {@code true} if the statement was executed successfully, + * otherwise {@code false} + * @throws SQLException if an error occurs + */ + public abstract boolean insert(String sql) throws SQLException; + + /** + * Executes the given UPDATE-statement. + * + * @param sql the sql statement + * @return {@code true} if the statement was executed successfully, + * otherwise {@code false} + * @throws SQLException if an error occurs + */ + public abstract boolean update(String sql) throws SQLException; + + /** + * Executes the given CREATE-statement. + * + * @param sql the sql statement + * @return {@code true} if the statement was executed successfully, + * otherwise {@code false} + * @throws SQLException if an error occurs + */ + public abstract boolean create(String sql) throws SQLException; + + /** + * Executes the given DROP-statement. + * + * @param sql the sql statement + * @return {@code true} if the statement was executed successfully, + * otherwise {@code false} + * @throws SQLException if an error occurs + */ + public abstract boolean drop(String sql) throws SQLException; + + /** + * Executes the given sql statement and returns the result wrapped into + * an object. + *

+ * It is strongly recommended to NOT return a {@link java.sql.ResultSet} + * which was opened within a try-catch block, because it will be closed + * automatically and no data would be read. + * + * @param sql the sql statement + * @return {@code true} if the statement was executed successfully, + * otherwise {@code false} + * @throws SQLException if an error occurs + */ + public abstract Object raw(String sql) throws SQLException; + + /** + * @return the data source + */ + public SQLSource getSource() { + return source; + } + + /** + * Sets a new data source instance. + * + * @param source the new data source. + */ + public void setSource(SQLSource source) { + this.source = Objects.requireNonNull(source); + } + +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLSource.java b/src/main/java/org/proto4j/redis/sql/SQLSource.java new file mode 100644 index 0000000..86e4309 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLSource.java @@ -0,0 +1,46 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql; //@date 29.08.2022 + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public abstract class SQLSource { + + private final SQLConfiguration configuration; + + protected SQLSource(SQLConfiguration configuration) {this.configuration = configuration;} + + public abstract Connection getConnection() throws SQLException; + + public abstract PreparedStatement prepare(String sql) throws SQLException; + + public abstract boolean isConnected(); + + public SQLConfiguration getConfiguration() { + return configuration; + } +} diff --git a/src/main/java/org/proto4j/redis/sql/SQLValidator.java b/src/main/java/org/proto4j/redis/sql/SQLValidator.java new file mode 100644 index 0000000..32e1b73 --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/SQLValidator.java @@ -0,0 +1,44 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +/** + * Represents a {@code Predicate} that tries to verify the given + * sql statement. This interface is intended to reduce sql injections by providing + * an instance that verifies the given string. + * + * @since 1.0 + */ +@FunctionalInterface +public interface SQLValidator { + + /** + * Evaluates this validator on the given sql statement. + * + * @param sql the input string + * @return true if no errors are detected, otherwise false + */ + public boolean verify(String sql); +} diff --git a/src/main/java/org/proto4j/redis/sql/Validator.java b/src/main/java/org/proto4j/redis/sql/Validator.java new file mode 100644 index 0000000..76cd9bb --- /dev/null +++ b/src/main/java/org/proto4j/redis/sql/Validator.java @@ -0,0 +1,44 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.redis.sql;//@date 31.08.2022 + +import java.lang.annotation.*; + +/** + * Indicates the annotated class should use the given {@link SQLValidator} + * to verify sql statements. + * + * @since 1.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Validator { + + /** + * @return the class type of the {@link SQLValidator} that should be used. + */ + Class value(); +} diff --git a/src/main/test/org/proto4j/test/redis/SqliteTest.java b/src/main/test/org/proto4j/test/redis/SqliteTest.java new file mode 100644 index 0000000..1554722 --- /dev/null +++ b/src/main/test/org/proto4j/test/redis/SqliteTest.java @@ -0,0 +1,44 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.test.redis; //@date 03.09.2022 + +import org.proto4j.redis.Redis; +import org.proto4j.redis.sql.SQLConfiguration; +import org.proto4j.redis.sqlite.SQLiteConfiguration; + +import java.util.List; + +public class SqliteTest { + + public static void main(String[] args) { + SQLConfiguration configuration = new SQLiteConfiguration("mydb"); + + UserStorage storage = Redis.create(UserStorage.class, configuration); + + List users = storage.fetchAll("user_table"); + System.out.println(users.size()); + } + +} diff --git a/src/main/test/org/proto4j/test/redis/User.java b/src/main/test/org/proto4j/test/redis/User.java new file mode 100644 index 0000000..91d4ae6 --- /dev/null +++ b/src/main/test/org/proto4j/test/redis/User.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.test.redis; //@date 03.09.2022 + +import org.proto4j.redis.sql.Entity; +import org.proto4j.redis.sql.SQL; + +@Entity +public class User { + + @SQL.Column("id") + private int id; + + @SQL.Column("name") + private String name; + + @Override + public String toString() { + return "User{id=" + id + ", name='" + name + "'}"; + } +} diff --git a/src/main/test/org/proto4j/test/redis/UserStorage.java b/src/main/test/org/proto4j/test/redis/UserStorage.java new file mode 100644 index 0000000..6a5ab9f --- /dev/null +++ b/src/main/test/org/proto4j/test/redis/UserStorage.java @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.proto4j.test.redis;//@date 03.09.2022 + +import org.proto4j.redis.sql.Param; +import org.proto4j.redis.sql.SQL; +import org.proto4j.redis.sqlite.SQLiteFactory; + +import java.util.List; + +@SQL(SQLiteFactory.FACTORY_PREFIX) +public interface UserStorage { + + @SQL.Select("select * from {table};") + public List fetchAll(@Param("table") String table); + + +}