From 6d2950797310a244ce5bd23d6bb55c2fa52b6913 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 24 May 2022 09:16:44 +0200 Subject: [PATCH 01/16] WIP commonDB Signed-off-by: Lixfel --- build.gradle | 2 + src/de/steamwar/ImplementationProvider.java | 34 +++ src/de/steamwar/sql/SQLConfig.java | 34 +++ src/de/steamwar/sql/Statement.java | 226 ++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 src/de/steamwar/ImplementationProvider.java create mode 100644 src/de/steamwar/sql/SQLConfig.java create mode 100644 src/de/steamwar/sql/Statement.java diff --git a/build.gradle b/build.gradle index 41ba86c..1edd6eb 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,8 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' + + implementation 'org.xerial:sqlite-jdbc:3.36.0' } task buildProject { diff --git a/src/de/steamwar/ImplementationProvider.java b/src/de/steamwar/ImplementationProvider.java new file mode 100644 index 0000000..1e8baec --- /dev/null +++ b/src/de/steamwar/ImplementationProvider.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +import java.lang.reflect.InvocationTargetException; + +public class ImplementationProvider { + private ImplementationProvider() {} + + public static T getImpl(String className) { + try { + return (T) Class.forName(className).getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { + throw new SecurityException("Could not load SQLConfigProviderImpl", e); + } + } +} diff --git a/src/de/steamwar/sql/SQLConfig.java b/src/de/steamwar/sql/SQLConfig.java new file mode 100644 index 0000000..2420a0e --- /dev/null +++ b/src/de/steamwar/sql/SQLConfig.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.ImplementationProvider; + +import java.util.logging.Logger; + +public interface SQLConfig { + SQLConfig impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLConfigImpl"); + + Logger getLogger(); + + int maxConnections(); + + +} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java new file mode 100644 index 0000000..4ea6142 --- /dev/null +++ b/src/de/steamwar/sql/Statement.java @@ -0,0 +1,226 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2022 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.sql.*; +import java.util.*; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Statement implements AutoCloseable { + + private static final Logger logger = SQLConfig.impl.getLogger(); + + private static final List statements = new ArrayList<>(); + private static final Deque connections = new ArrayDeque<>(); + private static final int MAX_CONNECTIONS; + private static final Supplier conProvider; + static { + File file = new File(new File("plugins", "SpigotCore"), "mysql.properties"); + + if(file.exists()) { + Properties properties = new Properties(); + try { + properties.load(new FileReader(file)); + } catch (IOException e) { + throw new SecurityException("Could not load SQL connection", e); + } + + String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?autoReconnect=true&useServerPrepStmts=true"; + String user = properties.getProperty("user"); + String password = properties.getProperty("password"); + + MAX_CONNECTIONS = SQLConfig.impl.maxConnections(); + conProvider = () -> { + try { + return DriverManager.getConnection(url, user, password); + } catch (SQLException e) { + throw new SecurityException("Could not create MySQL connection", e); + } + }; + } else { + MAX_CONNECTIONS = 1; + Connection connection; + + try { + connection = DriverManager.getConnection("jdbc:sqlite:standalone.db"); + } catch (SQLException e) { + throw new SecurityException("Could not create sqlite connection", e); + } + //TODO ensure schema + + conProvider = () -> connection; + } + } + + private static int connectionBudget = MAX_CONNECTIONS; + + public static void closeAll() { + synchronized (connections) { + while(connectionBudget < MAX_CONNECTIONS) { + if(connections.isEmpty()) + waitOnConnections(); + else + closeConnection(aquireConnection()); + } + } + } + + private final String sql; + private final Map cachedStatements = new HashMap<>(); + + public Statement(String sql) { + this.sql = sql; + synchronized (statements) { + statements.add(this); + } + } + + public T select(ResultSetUser user, Object... objects) { + return withConnection(st -> { + ResultSet rs = st.executeQuery(); + T result = user.use(rs); + rs.close(); + return result; + }, objects); + } + + public void update(Object... objects) { + withConnection(PreparedStatement::executeUpdate, objects); + } + + private T withConnection(SQLRunnable runnable, Object... objects) { + Connection connection = aquireConnection(); + + T result; + try { + result = tryWithConnection(connection, runnable, objects); + } catch (SQLException e) { + closeConnection(connection); + connection = aquireConnection(); + try { + result = tryWithConnection(connection, runnable, objects); + } catch (SQLException ex) { + closeConnection(connection); + throw new SecurityException("Could not execute statement", ex); + } + } + + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + + return result; + } + + private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { + PreparedStatement st = cachedStatements.get(connection); + if(st == null) { + st = connection.prepareStatement(sql); + cachedStatements.put(connection, st); + } + + for (int i = 0; i < objects.length; i++) { + st.setObject(i + 1, objects[i]); + } + + return runnable.run(st); + } + + @Override + public void close() { + cachedStatements.values().forEach(st -> closeStatement(st, false)); + cachedStatements.clear(); + synchronized (statements) { + statements.remove(this); + } + } + + private void close(Connection connection) { + PreparedStatement st = cachedStatements.remove(connection); + if(st != null) + closeStatement(st, true); + } + + private static Connection aquireConnection() { + synchronized (connections) { + if(connections.isEmpty() && connectionBudget == 0) + waitOnConnections(); + + if(!connections.isEmpty()) { + return connections.pop(); + } else { + Connection connection = conProvider.get(); + connectionBudget--; + return connection; + } + } + } + + private static void closeConnection(Connection connection) { + synchronized (statements) { + for (Statement statement : statements) { + statement.close(connection); + } + } + try { + connection.close(); + } catch (SQLException e) { + logger.log(Level.INFO, "Could not close connection", e); + } + + synchronized (connections) { + connectionBudget++; + connections.notify(); + } + } + + private static void waitOnConnections() { + synchronized (connections) { + try { + connections.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private static void closeStatement(PreparedStatement st, boolean silent) { + try { + st.close(); + } catch (SQLException e) { + if(!silent) + logger.log(Level.INFO, "Could not close statement", e); + } + } + + public interface ResultSetUser { + T use(ResultSet rs) throws SQLException; + } + + private interface SQLRunnable { + T run(PreparedStatement st) throws SQLException; + } +} -- 2.39.2 From f6197978243d82cc078ba3a33b726f2b2dfc3f56 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 14:32:38 +0200 Subject: [PATCH 02/16] Fix enum mapper --- src/de/steamwar/command/SWCommandUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index e038e23..73468fa 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -218,12 +218,12 @@ public class SWCommandUtils { public static >, K> T createEnumMapper(Class> enumClass) { Map> enumMap = new HashMap<>(); for (Enum e : enumClass.getEnumConstants()) { - enumMap.put(e.name(), e); + enumMap.put(e.name().toLowerCase(), e); } return (T) new AbstractTypeMapper>() { @Override public Enum map(Object commandSender, String[] previousArguments, String s) { - return enumMap.get(s); + return enumMap.get(s.toLowerCase()); } @Override -- 2.39.2 From 800e5073304ce7f353f497830389755b2bd250ee Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 14:52:21 +0200 Subject: [PATCH 03/16] Fix SWCommandUtils Add SWTypeMapperCreator --- src/de/steamwar/command/SWCommandUtils.java | 36 ++++++------------- .../steamwar/command/SWTypeMapperCreator.java | 28 +++++++++++++++ 2 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 src/de/steamwar/command/SWTypeMapperCreator.java diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 73468fa..331b9f9 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -38,7 +38,11 @@ public class SWCommandUtils { @Getter private final Map> GUARD_FUNCTIONS = new HashMap<>(); - static { + private SWTypeMapperCreator swTypeMapperCreator; + + public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { + SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + addMapper(boolean.class, Boolean.class, createMapper(s -> { if (s.equalsIgnoreCase("true")) return true; if (s.equalsIgnoreCase("false")) return false; @@ -197,22 +201,12 @@ public class SWCommandUtils { return createMapper((s) -> strings.contains(s) ? s : null, s -> strings); } - public static , K, V> T createMapper(Function mapper, Function> tabCompleter) { + public static , K, V> T createMapper(Function mapper, Function> tabCompleter) { return createMapper(mapper, (commandSender, s) -> tabCompleter.apply(s)); } - public static , K, V> T createMapper(Function mapper, BiFunction> tabCompleter) { - return (T) new AbstractTypeMapper() { - @Override - public V map(K commandSender, String[] previousArguments, String s) { - return mapper.apply(s); - } - - @Override - public List tabCompletes(K commandSender, String[] previous, String s) { - return tabCompleter.apply(commandSender, s); - } - }; + public static , K, V> T createMapper(Function mapper, BiFunction> tabCompleter) { + return (T) swTypeMapperCreator.createTypeMapper(mapper, tabCompleter); } public static >, K> T createEnumMapper(Class> enumClass) { @@ -220,17 +214,7 @@ public class SWCommandUtils { for (Enum e : enumClass.getEnumConstants()) { enumMap.put(e.name().toLowerCase(), e); } - return (T) new AbstractTypeMapper>() { - @Override - public Enum map(Object commandSender, String[] previousArguments, String s) { - return enumMap.get(s.toLowerCase()); - } - - @Override - public Collection tabCompletes(Object commandSender, String[] previousArguments, String s) { - return enumMap.keySet(); - } - }; + return createMapper(s -> enumMap.get(s.toLowerCase()), (k, s) -> enumMap.keySet()); } private static Function numberMapper(Function mapper) { @@ -249,7 +233,7 @@ public class SWCommandUtils { }; } - private static Function> numberCompleter(Function mapper) { + private static Function> numberCompleter(Function mapper) { return s -> numberMapper(mapper).apply(s) != null ? Collections.singletonList(s) : Collections.emptyList(); diff --git a/src/de/steamwar/command/SWTypeMapperCreator.java b/src/de/steamwar/command/SWTypeMapperCreator.java new file mode 100644 index 0000000..33c5dcb --- /dev/null +++ b/src/de/steamwar/command/SWTypeMapperCreator.java @@ -0,0 +1,28 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +public interface SWTypeMapperCreator, K, V> { + T createTypeMapper(Function mapper, BiFunction> tabCompleter); +} -- 2.39.2 From 57ab89e058b2feda1445981c3b48dbe356c9294f Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 14:59:00 +0200 Subject: [PATCH 04/16] Fix SWCommandUtils --- src/de/steamwar/command/SWCommandUtils.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 331b9f9..6e486b1 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -38,11 +38,19 @@ public class SWCommandUtils { @Getter private final Map> GUARD_FUNCTIONS = new HashMap<>(); - private SWTypeMapperCreator swTypeMapperCreator; + private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() { + @Override + public Object map(Object sender, String[] previousArguments, String s) { + return mapper.apply(s); + } - public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { - SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + @Override + public Collection tabCompletes(Object sender, String[] previousArguments, String s) { + return tabCompleter.apply(sender, s); + } + }; + static { addMapper(boolean.class, Boolean.class, createMapper(s -> { if (s.equalsIgnoreCase("true")) return true; if (s.equalsIgnoreCase("false")) return false; @@ -55,6 +63,10 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> s, Collections::singletonList)); } + public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { + SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + } + private static void addMapper(Class clazz, Class alternativeClazz, AbstractTypeMapper mapper) { MAPPER_FUNCTIONS.put(clazz.getTypeName(), mapper); MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); -- 2.39.2 From b25bf2a79cbf2ff32478123dda728733bb840018 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 15:04:51 +0200 Subject: [PATCH 05/16] Fix SWCommandUtils --- src/de/steamwar/command/SWCommandUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 6e486b1..aaf0c7a 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -46,7 +46,7 @@ public class SWCommandUtils { @Override public Collection tabCompletes(Object sender, String[] previousArguments, String s) { - return tabCompleter.apply(sender, s); + return ((BiFunction>) tabCompleter).apply(sender, s); } }; -- 2.39.2 From d6c29a25b917c85e68521421ee8e588c8195977a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 8 Jun 2022 08:00:31 +0200 Subject: [PATCH 06/16] More commonDB Signed-off-by: Lixfel --- src/de/steamwar/ImplementationProvider.java | 2 +- src/de/steamwar/sql/Statement.java | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/de/steamwar/ImplementationProvider.java b/src/de/steamwar/ImplementationProvider.java index 1e8baec..e5b795e 100644 --- a/src/de/steamwar/ImplementationProvider.java +++ b/src/de/steamwar/ImplementationProvider.java @@ -28,7 +28,7 @@ public class ImplementationProvider { try { return (T) Class.forName(className).getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { - throw new SecurityException("Could not load SQLConfigProviderImpl", e); + throw new SecurityException("Could not load implementation", e); } } } diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index 4ea6142..a1d55f0 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -64,11 +64,12 @@ public class Statement implements AutoCloseable { Connection connection; try { + Class.forName("org.sqlite.JDBC"); connection = DriverManager.getConnection("jdbc:sqlite:standalone.db"); - } catch (SQLException e) { + //TODO schema + } catch (SQLException | ClassNotFoundException e) { throw new SecurityException("Could not create sqlite connection", e); } - //TODO ensure schema conProvider = () -> connection; } @@ -117,14 +118,17 @@ public class Statement implements AutoCloseable { try { result = tryWithConnection(connection, runnable, objects); } catch (SQLException e) { - closeConnection(connection); - connection = aquireConnection(); try { - result = tryWithConnection(connection, runnable, objects); + if(connection.isClosed() || !connection.isValid(1)) { + closeConnection(connection); + return withConnection(runnable, objects); + } } catch (SQLException ex) { closeConnection(connection); - throw new SecurityException("Could not execute statement", ex); + throw new SecurityException("Could not test connection validity", ex); } + + throw new SecurityException("Failing sql statement", e); } synchronized (connections) { -- 2.39.2 From 29bc98889a38e181e007519ae11245ef8b447895 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 11 Jun 2022 14:20:14 +0200 Subject: [PATCH 07/16] Prototyping new db interface Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 39 ++++++++++++++ src/de/steamwar/sql/Row.java | 35 ++++++++++++ src/de/steamwar/sql/Statement.java | 4 ++ src/de/steamwar/sql/Table.java | 87 ++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 src/de/steamwar/sql/Field.java create mode 100644 src/de/steamwar/sql/Row.java create mode 100644 src/de/steamwar/sql/Table.java diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java new file mode 100644 index 0000000..8424665 --- /dev/null +++ b/src/de/steamwar/sql/Field.java @@ -0,0 +1,39 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +public class Field { + + private final String identifier; + private final Class type; + + public Field(String identifier, Class type) { + this.identifier = identifier; + this.type = type; + } + + public String identifier() { + return identifier; + } + + public Class type() { + return type; + } +} diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java new file mode 100644 index 0000000..c9ae439 --- /dev/null +++ b/src/de/steamwar/sql/Row.java @@ -0,0 +1,35 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +public class Row { + + private final Table table; + private Object[] values; + + public Row(Table table, Object[] values) { + this.table = table; + this.values = values; + } + + void update(Field field, Object value) { + + } +} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index a1d55f0..a54a4ee 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -111,6 +111,10 @@ public class Statement implements AutoCloseable { withConnection(PreparedStatement::executeUpdate, objects); } + public String getSql() { + return sql; + } + private T withConnection(SQLRunnable runnable, Object... objects) { Connection connection = aquireConnection(); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java new file mode 100644 index 0000000..239b3f8 --- /dev/null +++ b/src/de/steamwar/sql/Table.java @@ -0,0 +1,87 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.util.*; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class Table { + + private final String name; + private final Field key; + private final Field[] fields; + + private final Map selectBy = new HashMap<>(); + + public Table(String name, Field key, Field... fields) { + this.name = name; + this.key = key; + this.fields = fields; + } + + public Row selectSingle(Field field, Object value) { + return select(rs -> { + if(!rs.next()) + return null; + + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].type().cast(rs.getObject(i)); + } + + if(rs.next()) + SQLConfig.impl.getLogger().log(Level.WARNING, "Statement returned more than one result " + selectBy.get(field).getSql() + " for value " + value); + + return new Row(this, values); + }, field, value); + } + + public List selectMulti(Field field, Object value) { + return select(rs -> { + List result = new ArrayList<>(); + while(rs.next()) { + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].type().cast(rs.getObject(i)); + } + + result.add(new Row(this, values)); + } + return result; + }, field, value); + } + + public void create() { + //TODO and alter table + } + + public Field getKey() { + return key; + } + + private T select(Statement.ResultSetUser u, Field field, Object value) { + Statement statement; + synchronized (selectBy) { + statement = selectBy.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + } + return statement.select(u, value); + } +} -- 2.39.2 From 4f719e30ac67aceb153a206ef3b7202305a3700b Mon Sep 17 00:00:00 2001 From: Lixfel Date: Thu, 23 Jun 2022 14:58:33 +0200 Subject: [PATCH 08/16] First basic untested implementation Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 45 ++++++++++-- src/de/steamwar/sql/Row.java | 8 ++- src/de/steamwar/sql/SqlTypeParser.java | 27 +++++++ src/de/steamwar/sql/Statement.java | 2 +- src/de/steamwar/sql/Table.java | 99 ++++++++++++++++---------- 5 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 src/de/steamwar/sql/SqlTypeParser.java diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 8424665..2b40c8f 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -19,21 +19,58 @@ package de.steamwar.sql; -public class Field { +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class Field { + + private static final Map, String> sqlTypeMapping = new HashMap<>(); + private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); + + public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { + sqlTypeMapping.put(clazz, sqlType); + sqlTypeParser.put(clazz, parser); + } + + static { + addTypeMapping(String.class, "TEXT", (rs, field) -> rs.getString(field.identifier())); + addTypeMapping(boolean.class, "INTEGER(1)", (rs, field) -> rs.getBoolean(field.identifier())); + addTypeMapping(byte.class, "INTEGER(1)", (rs, field) -> rs.getByte(field.identifier())); + addTypeMapping(short.class, "INTEGER(2)", (rs, field) -> rs.getShort(field.identifier())); + addTypeMapping(int.class, "INTEGER(4)", (rs, field) -> rs.getInt(field.identifier())); + addTypeMapping(long.class, "INTEGER(8)", (rs, field) -> rs.getLong(field.identifier())); + addTypeMapping(float.class, "REAL", (rs, field) -> rs.getFloat(field.identifier())); + addTypeMapping(double.class, "REAL", (rs, field) -> rs.getDouble(field.identifier())); + } private final String identifier; - private final Class type; + private final Class type; - public Field(String identifier, Class type) { + private final SqlTypeParser parser; + private final String sqlType; + + public Field(String identifier, Class type) { this.identifier = identifier; this.type = type; + this.parser = (SqlTypeParser) sqlTypeParser.get(type); + this.sqlType = sqlTypeMapping.get(type); } public String identifier() { return identifier; } - public Class type() { + public Class type() { return type; } + + public String sqlType() { + return sqlType; + } + + public T parse(ResultSet rs) throws SQLException { + return parser.parse(rs, this); + } } diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java index c9ae439..afd7056 100644 --- a/src/de/steamwar/sql/Row.java +++ b/src/de/steamwar/sql/Row.java @@ -24,12 +24,16 @@ public class Row { private final Table table; private Object[] values; - public Row(Table table, Object[] values) { + public Row(Table table, Object... values) { this.table = table; this.values = values; } - void update(Field field, Object value) { + private T get(Field field) { + return (T) values[table.getFieldId(field)]; + } + void update(Field[] fields, Object... values) { + table.update(values[table.keyId()], fields, values); } } diff --git a/src/de/steamwar/sql/SqlTypeParser.java b/src/de/steamwar/sql/SqlTypeParser.java new file mode 100644 index 0000000..377097c --- /dev/null +++ b/src/de/steamwar/sql/SqlTypeParser.java @@ -0,0 +1,27 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface SqlTypeParser { + T parse(ResultSet rs, Field field) throws SQLException; +} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index a54a4ee..bc9e32f 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -47,7 +47,7 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not load SQL connection", e); } - String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?autoReconnect=true&useServerPrepStmts=true"; + String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?useServerPrepStmts=true"; String user = properties.getProperty("user"); String password = properties.getProperty("password"); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index 239b3f8..4027532 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -19,69 +19,96 @@ package de.steamwar.sql; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.*; -import java.util.logging.Level; import java.util.stream.Collectors; public class Table { private final String name; - private final Field key; - private final Field[] fields; + private final Field key; + private final Field[] fields; + private final Map, Integer> fieldIds = new HashMap<>(); + private final int keyId; - private final Map selectBy = new HashMap<>(); + private final Map, Statement> cachedSelect = new HashMap<>(); + private final Map[], Statement> cachedInsert = new HashMap<>(); + private final Map[], Statement> cachedUpdate = new HashMap<>(); - public Table(String name, Field key, Field... fields) { + public Table(String name, Field key, Field... fields) { this.name = name; this.key = key; this.fields = fields; + for(int i = 0; i < fields.length; i++) { + fieldIds.put(fields[i], i); + } + keyId = fieldIds.get(key); } - public Row selectSingle(Field field, Object value) { + public Row selectSingle(Field field, Object value) { return select(rs -> { - if(!rs.next()) - return null; - - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].type().cast(rs.getObject(i)); - } - if(rs.next()) - SQLConfig.impl.getLogger().log(Level.WARNING, "Statement returned more than one result " + selectBy.get(field).getSql() + " for value " + value); - - return new Row(this, values); + return read(rs); + return null; }, field, value); } - public List selectMulti(Field field, Object value) { + public List selectMulti(Field field, Object value) { return select(rs -> { List result = new ArrayList<>(); - while(rs.next()) { - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].type().cast(rs.getObject(i)); - } + while(rs.next()) + result.add(read(rs)); - result.add(new Row(this, values)); - } return result; }, field, value); } - public void create() { - //TODO and alter table - } - - public Field getKey() { - return key; - } - - private T select(Statement.ResultSetUser u, Field field, Object value) { + public void insert(Field[] fields, Object... values) { Statement statement; - synchronized (selectBy) { - statement = selectBy.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + synchronized (cachedInsert) { + statement = cachedInsert.computeIfAbsent(fields, fs -> new Statement("INSERT INTO " + name + " (" + Arrays.stream(fs).map(Field::identifier).collect(Collectors.joining(", ")) + ") VALUES (" + Arrays.stream(fs).map(f -> "?").collect(Collectors.joining(", ")) + ")")); + } + statement.update(values); + } + + public void update(Object keyvalue, Field[] fields, Object... values) { + Statement statement; + synchronized (cachedUpdate) { + statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + key.identifier() + " = ?")); + } + statement.update(values, keyvalue); + } + + public void create() { + //TODO syntax mysql/sqlite + try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (field == key ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + statement.update(); + } + } + + public int keyId() { + return keyId; + } + + public int getFieldId(Field field) { + return fieldIds.get(field); + } + + private T select(Statement.ResultSetUser u, Field field, Object value) { + Statement statement; + synchronized (cachedSelect) { + statement = cachedSelect.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); } return statement.select(u, value); } + + private Row read(ResultSet rs) throws SQLException { + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].parse(rs); + } + + return new Row(this, values); + } } -- 2.39.2 From c5bed0acfa10d7f24a6a100a38d5afd649eafa75 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Fri, 1 Jul 2022 21:37:33 +0200 Subject: [PATCH 09/16] WIP commonDb Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 1 + src/de/steamwar/sql/Row.java | 2 +- src/de/steamwar/sql/Table.java | 38 +++++++++++++++++----------------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 2b40c8f..2180ca8 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -29,6 +29,7 @@ public class Field { private static final Map, String> sqlTypeMapping = new HashMap<>(); private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); + //TODO andere Richtung Objekt -> SQL public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { sqlTypeMapping.put(clazz, sqlType); sqlTypeParser.put(clazz, parser); diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java index afd7056..53c6db2 100644 --- a/src/de/steamwar/sql/Row.java +++ b/src/de/steamwar/sql/Row.java @@ -34,6 +34,6 @@ public class Row { } void update(Field[] fields, Object... values) { - table.update(values[table.keyId()], fields, values); + table.update(values[table.keyIds()], fields, values); } } diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index 4027532..b8971f4 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -27,41 +27,41 @@ import java.util.stream.Collectors; public class Table { private final String name; - private final Field key; + private final Set> keys; private final Field[] fields; private final Map, Integer> fieldIds = new HashMap<>(); - private final int keyId; + private final List keyIds; - private final Map, Statement> cachedSelect = new HashMap<>(); + private final Map[], Statement> cachedSelect = new HashMap<>(); private final Map[], Statement> cachedInsert = new HashMap<>(); private final Map[], Statement> cachedUpdate = new HashMap<>(); - public Table(String name, Field key, Field... fields) { + public Table(String name, Field[] keys, Field... fields) { this.name = name; - this.key = key; + this.keys = Arrays.stream(keys).collect(Collectors.toSet()); this.fields = fields; for(int i = 0; i < fields.length; i++) { fieldIds.put(fields[i], i); } - keyId = fieldIds.get(key); + keyIds = Arrays.stream(keys).map(fieldIds::get).collect(Collectors.toList()); } - public Row selectSingle(Field field, Object value) { + public Row selectSingle(Field[] fields, Object... values) { return select(rs -> { if(rs.next()) return read(rs); return null; - }, field, value); + }, fields, values); } - public List selectMulti(Field field, Object value) { + public List selectMulti(Field[] fields, Object... values) { return select(rs -> { List result = new ArrayList<>(); while(rs.next()) result.add(read(rs)); return result; - }, field, value); + }, fields, values); } public void insert(Field[] fields, Object... values) { @@ -72,35 +72,35 @@ public class Table { statement.update(values); } - public void update(Object keyvalue, Field[] fields, Object... values) { + public void update(Object[] keyvalues, Field[] fields, Object... values) { Statement statement; synchronized (cachedUpdate) { - statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + key.identifier() + " = ?")); + statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + keys.stream().map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); } - statement.update(values, keyvalue); + statement.update(values, keyvalues); } public void create() { //TODO syntax mysql/sqlite - try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (field == key ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (keys.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { statement.update(); } } - public int keyId() { - return keyId; + public List keyIds() { + return keyIds; } public int getFieldId(Field field) { return fieldIds.get(field); } - private T select(Statement.ResultSetUser u, Field field, Object value) { + private T select(Statement.ResultSetUser u, Field[] fields, Object... values) { Statement statement; synchronized (cachedSelect) { - statement = cachedSelect.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + statement = cachedSelect.computeIfAbsent(fields, fs -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); } - return statement.select(u, value); + return statement.select(u, values); } private Row read(ResultSet rs) throws SQLException { -- 2.39.2 From 9ccb678522ee43e37bbaab5876268214d4de2653 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Thu, 1 Sep 2022 17:17:39 +0200 Subject: [PATCH 10/16] WIP commonDB Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 65 ++-------- src/de/steamwar/sql/Row.java | 39 ------ src/de/steamwar/sql/SelectStatement.java | 72 +++++++++++ src/de/steamwar/sql/SqlTypeMapper.java | 100 ++++++++++++++++ src/de/steamwar/sql/SqlTypeParser.java | 27 ----- src/de/steamwar/sql/Statement.java | 13 +- src/de/steamwar/sql/Table.java | 145 +++++++++++++---------- 7 files changed, 273 insertions(+), 188 deletions(-) delete mode 100644 src/de/steamwar/sql/Row.java create mode 100644 src/de/steamwar/sql/SelectStatement.java create mode 100644 src/de/steamwar/sql/SqlTypeMapper.java delete mode 100644 src/de/steamwar/sql/SqlTypeParser.java diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 2180ca8..656fdd9 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -19,59 +19,16 @@ package de.steamwar.sql; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -public class Field { - - private static final Map, String> sqlTypeMapping = new HashMap<>(); - private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); - - //TODO andere Richtung Objekt -> SQL - public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { - sqlTypeMapping.put(clazz, sqlType); - sqlTypeParser.put(clazz, parser); - } - - static { - addTypeMapping(String.class, "TEXT", (rs, field) -> rs.getString(field.identifier())); - addTypeMapping(boolean.class, "INTEGER(1)", (rs, field) -> rs.getBoolean(field.identifier())); - addTypeMapping(byte.class, "INTEGER(1)", (rs, field) -> rs.getByte(field.identifier())); - addTypeMapping(short.class, "INTEGER(2)", (rs, field) -> rs.getShort(field.identifier())); - addTypeMapping(int.class, "INTEGER(4)", (rs, field) -> rs.getInt(field.identifier())); - addTypeMapping(long.class, "INTEGER(8)", (rs, field) -> rs.getLong(field.identifier())); - addTypeMapping(float.class, "REAL", (rs, field) -> rs.getFloat(field.identifier())); - addTypeMapping(double.class, "REAL", (rs, field) -> rs.getDouble(field.identifier())); - } - - private final String identifier; - private final Class type; - - private final SqlTypeParser parser; - private final String sqlType; - - public Field(String identifier, Class type) { - this.identifier = identifier; - this.type = type; - this.parser = (SqlTypeParser) sqlTypeParser.get(type); - this.sqlType = sqlTypeMapping.get(type); - } - - public String identifier() { - return identifier; - } - - public Class type() { - return type; - } - - public String sqlType() { - return sqlType; - } - - public T parse(ResultSet rs) throws SQLException { - return parser.parse(rs, this); - } +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Field { + String[] keys() default {}; + String def() default ""; + boolean nullable() default false; + boolean autoincrement() default false; } diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java deleted file mode 100644 index 53c6db2..0000000 --- a/src/de/steamwar/sql/Row.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2022 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.sql; - -public class Row { - - private final Table table; - private Object[] values; - - public Row(Table table, Object... values) { - this.table = table; - this.values = values; - } - - private T get(Field field) { - return (T) values[table.getFieldId(field)]; - } - - void update(Field[] fields, Object... values) { - table.update(values[table.keyIds()], fields, values); - } -} diff --git a/src/de/steamwar/sql/SelectStatement.java b/src/de/steamwar/sql/SelectStatement.java new file mode 100644 index 0000000..cc00ecc --- /dev/null +++ b/src/de/steamwar/sql/SelectStatement.java @@ -0,0 +1,72 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.lang.reflect.InvocationTargetException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class SelectStatement extends Statement { + private final Table table; + + SelectStatement(Table table, String... kfields) { + this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + } + + public SelectStatement(Table table, String sql) { + super(sql); + this.table = table; + } + + public T select(Object... values) { + return select(rs -> { + if (rs.next()) + return read(rs); + return null; + }, values); + } + + public List listSelect(Object... values) { + return select(rs -> { + List result = new ArrayList<>(); + while (rs.next()) + result.add(read(rs)); + + return result; + }, values); + } + + private T read(ResultSet rs) throws SQLException { + Object[] params = new Object[table.fields.length]; + for(int i = 0; i < params.length; i++) { + params[i] = table.fields[i].read(rs); + } + + try { + return table.constructor.newInstance(params); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new SecurityException(e); + } + } +} diff --git a/src/de/steamwar/sql/SqlTypeMapper.java b/src/de/steamwar/sql/SqlTypeMapper.java new file mode 100644 index 0000000..68d2ee8 --- /dev/null +++ b/src/de/steamwar/sql/SqlTypeMapper.java @@ -0,0 +1,100 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.Map; + +public final class SqlTypeMapper { + private static final Map, SqlTypeMapper> mappers = new IdentityHashMap<>(); + + public static SqlTypeMapper getMapper(Class clazz) { + return (SqlTypeMapper) mappers.get(clazz); + } + + static { + new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString); + new SqlTypeMapper<>(Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); + new SqlTypeMapper<>(Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); + new SqlTypeMapper<>(Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); + new SqlTypeMapper<>(Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); + new SqlTypeMapper<>(Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); + new SqlTypeMapper<>(Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); + new SqlTypeMapper<>(Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); + new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp); + } + + public static > void ordinalEnumMapper(Class type) { + T[] enumConstants = type.getEnumConstants(); + new SqlTypeMapper<>( + type, + "INTEGER(" + (int)Math.ceil(enumConstants.length/256.0) + ")", + (rs, identifier) -> enumConstants[rs.getInt(identifier)], + (st, index, value) -> st.setInt(index, value.ordinal()) + ); + } + + public static > void nameEnumMapper(Class type) { + new SqlTypeMapper<>( + type, + "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo) + ")", + (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)), + (st, index, value) -> st.setString(index, value.name()) + ); + } + + private final String sqlType; + private final SQLReader reader; + private final SQLWriter writer; + + public SqlTypeMapper(Class clazz, String sqlType, SQLReader reader, SQLWriter writer) { + this.sqlType = sqlType; + this.reader = reader; + this.writer = writer; + mappers.put(clazz, this); + } + + public T read(ResultSet rs, String identifier) throws SQLException { + return reader.read(rs, identifier); + } + + public void write(PreparedStatement st, int index, Object value) throws SQLException { + writer.write(st, index, (T) value); + } + + public String sqlType() { + return sqlType; + } + + @FunctionalInterface + public interface SQLReader { + T read(ResultSet rs, String identifier) throws SQLException; + } + + @FunctionalInterface + public interface SQLWriter { + void write(PreparedStatement st, int index, T value) throws SQLException; + } +} diff --git a/src/de/steamwar/sql/SqlTypeParser.java b/src/de/steamwar/sql/SqlTypeParser.java deleted file mode 100644 index 377097c..0000000 --- a/src/de/steamwar/sql/SqlTypeParser.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2022 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.sql; - -import java.sql.ResultSet; -import java.sql.SQLException; - -public interface SqlTypeParser { - T parse(ResultSet rs, Field field) throws SQLException; -} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index bc9e32f..9626f0e 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -24,6 +24,7 @@ import java.io.FileReader; import java.io.IOException; import java.sql.*; import java.util.*; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,8 +37,10 @@ public class Statement implements AutoCloseable { private static final Deque connections = new ArrayDeque<>(); private static final int MAX_CONNECTIONS; private static final Supplier conProvider; + static final Consumer> schemaCreator; + static { - File file = new File(new File("plugins", "SpigotCore"), "mysql.properties"); + File file = new File(System.getProperty("user.home"), "mysql.properties"); if(file.exists()) { Properties properties = new Properties(); @@ -59,19 +62,20 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create MySQL connection", e); } }; + schemaCreator = table -> {}; } else { MAX_CONNECTIONS = 1; Connection connection; try { Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:standalone.db"); - //TODO schema + connection = DriverManager.getConnection("jdbc:sqlite:" + System.getProperty("user.home") + "/standalone.db"); } catch (SQLException | ClassNotFoundException e) { throw new SecurityException("Could not create sqlite connection", e); } conProvider = () -> connection; + schemaCreator = Table::ensureExistanceInSqlite; } } @@ -151,7 +155,8 @@ public class Statement implements AutoCloseable { } for (int i = 0; i < objects.length; i++) { - st.setObject(i + 1, objects[i]); + Object o = objects[i]; + SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o); } return runnable.run(st); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index b8971f4..74dfc83 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -19,96 +19,113 @@ package de.steamwar.sql; +import java.lang.reflect.Constructor; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; -public class Table { +public class Table { + public static final String PRIMARY = "primary"; - private final String name; - private final Set> keys; - private final Field[] fields; - private final Map, Integer> fieldIds = new HashMap<>(); - private final List keyIds; + final String name; + final TableField[] fields; + private final Map> fieldsByIdentifier = new HashMap<>(); + final Constructor constructor; - private final Map[], Statement> cachedSelect = new HashMap<>(); - private final Map[], Statement> cachedInsert = new HashMap<>(); - private final Map[], Statement> cachedUpdate = new HashMap<>(); + private final Map[]> keys; - public Table(String name, Field[] keys, Field... fields) { + + public Table(Class clazz) { + this(clazz, clazz.getSimpleName()); + } + + public Table(Class clazz, String name) { this.name = name; - this.keys = Arrays.stream(keys).collect(Collectors.toSet()); - this.fields = fields; - for(int i = 0; i < fields.length; i++) { - fieldIds.put(fields[i], i); + this.fields = Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Field.class)).map(TableField::new).toArray(TableField[]::new); + try { + this.constructor = clazz.getDeclaredConstructor(Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Field.class)).map(java.lang.reflect.Field::getType).toArray(Class[]::new)); + } catch (NoSuchMethodException e) { + throw new SecurityException(e); } - keyIds = Arrays.stream(keys).map(fieldIds::get).collect(Collectors.toList()); - } - public Row selectSingle(Field[] fields, Object... values) { - return select(rs -> { - if(rs.next()) - return read(rs); - return null; - }, fields, values); - } + keys = Arrays.stream(fields).flatMap(field -> Arrays.stream(field.field.keys())).distinct().collect(Collectors.toMap(Function.identity(), key -> Arrays.stream(fields).filter(field -> Arrays.asList(field.field.keys()).contains(key)).toArray(TableField[]::new))); - public List selectMulti(Field[] fields, Object... values) { - return select(rs -> { - List result = new ArrayList<>(); - while(rs.next()) - result.add(read(rs)); - - return result; - }, fields, values); - } - - public void insert(Field[] fields, Object... values) { - Statement statement; - synchronized (cachedInsert) { - statement = cachedInsert.computeIfAbsent(fields, fs -> new Statement("INSERT INTO " + name + " (" + Arrays.stream(fs).map(Field::identifier).collect(Collectors.joining(", ")) + ") VALUES (" + Arrays.stream(fs).map(f -> "?").collect(Collectors.joining(", ")) + ")")); + for (TableField field : fields) { + fieldsByIdentifier.put(field.identifier, field); } - statement.update(values); + + Statement.schemaCreator.accept(this); } - public void update(Object[] keyvalues, Field[] fields, Object... values) { - Statement statement; - synchronized (cachedUpdate) { - statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + keys.stream().map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); - } - statement.update(values, keyvalues); + + public SelectStatement select(String name) { + return selectFields(keyFields(name)); + } + public SelectStatement selectFields(String... kfields) { + return new SelectStatement<>(this, kfields); } - public void create() { - //TODO syntax mysql/sqlite - try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (keys.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + public Statement update(String name, String... fields) { + return updateFields(fields, keyFields(name)); + } + + public Statement updateField(String field, String... kfields) { + return updateFields(new String[]{field}, kfields); + } + + public Statement updateFields(String[] fields, String... kfields) { + return new Statement("UPDATE " + name + " SET " + Arrays.stream(fields).map(f -> f + " = ?").collect(Collectors.joining(", ")) + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + } + + public Statement insert(String name) { + return insertFields(keyFields(name)); + } + + public Statement insertFields(String... fields) { + List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f).field.keys().length == 0).collect(Collectors.toList()); + return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", ")))); + } + + public Statement deleteWithKey(String name) { + return delete(keyFields(name)); + } + + public Statement delete(String... kfields) { + return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + } + + void ensureExistanceInSqlite() { + List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); + try (Statement statement = new Statement( + "CREATE TABLE IF NOT EXISTS " + name + "(" + + Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? "" : " NOT NULL DEFAULT NULL") + (!field.field.nullable() && field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + + keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + + ") STRICT, WITHOUT ROWID")) { statement.update(); } } - public List keyIds() { - return keyIds; + private String[] keyFields(String name) { + return Arrays.stream(keys.get(name)).map(f -> f.identifier).toArray(String[]::new); } - public int getFieldId(Field field) { - return fieldIds.get(field); - } + static class TableField { - private T select(Statement.ResultSetUser u, Field[] fields, Object... values) { - Statement statement; - synchronized (cachedSelect) { - statement = cachedSelect.computeIfAbsent(fields, fs -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); - } - return statement.select(u, values); - } + final String identifier; - private Row read(ResultSet rs) throws SQLException { - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].parse(rs); + final SqlTypeMapper mapper; + private final Field field; + + private TableField(java.lang.reflect.Field field) { + this.identifier = field.getName(); + this.mapper = (SqlTypeMapper) SqlTypeMapper.getMapper(field.getDeclaringClass()); + this.field = field.getAnnotation(Field.class); } - return new Row(this, values); + T read(ResultSet rs) throws SQLException { + return mapper.read(rs, identifier); + } } } -- 2.39.2 From c1e175c3e84280c160ae5c7f84e44805f159bbf3 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Thu, 8 Sep 2022 10:42:57 +0200 Subject: [PATCH 11/16] WIP current state --- src/de/steamwar/sql/BauweltMember.java | 79 +++++++++ src/de/steamwar/sql/Event.java | 72 +++++++++ src/de/steamwar/sql/EventFight.java | 72 +++++++++ src/de/steamwar/sql/Field.java | 34 ---- src/de/steamwar/sql/Fight.java | 61 +++++++ src/de/steamwar/sql/FightPlayer.java | 49 ++++++ src/de/steamwar/sql/NoClipboardException.java | 23 +++ src/de/steamwar/sql/NodeMember.java | 69 ++++++++ src/de/steamwar/sql/Replay.java | 73 +++++++++ src/de/steamwar/sql/SQLConfig.java | 34 ---- src/de/steamwar/sql/SQLWrapper.java | 31 ++++ src/de/steamwar/sql/SchematicType.java | 116 +++++++++++++ src/de/steamwar/sql/SteamwarUser.java | 153 ++++++++++++++++++ src/de/steamwar/sql/UserGroup.java | 45 ++++++ src/de/steamwar/sql/internal/Field.java | 34 ++++ src/de/steamwar/sql/internal/SQLConfig.java | 34 ++++ .../sql/{ => internal}/SelectStatement.java | 26 +-- .../sql/{ => internal}/SqlTypeMapper.java | 58 ++++--- .../sql/{ => internal}/Statement.java | 54 ++++--- src/de/steamwar/sql/{ => internal}/Table.java | 42 +++-- 20 files changed, 1018 insertions(+), 141 deletions(-) create mode 100644 src/de/steamwar/sql/BauweltMember.java create mode 100644 src/de/steamwar/sql/Event.java create mode 100644 src/de/steamwar/sql/EventFight.java delete mode 100644 src/de/steamwar/sql/Field.java create mode 100644 src/de/steamwar/sql/Fight.java create mode 100644 src/de/steamwar/sql/FightPlayer.java create mode 100644 src/de/steamwar/sql/NoClipboardException.java create mode 100644 src/de/steamwar/sql/NodeMember.java create mode 100644 src/de/steamwar/sql/Replay.java delete mode 100644 src/de/steamwar/sql/SQLConfig.java create mode 100644 src/de/steamwar/sql/SQLWrapper.java create mode 100644 src/de/steamwar/sql/SchematicType.java create mode 100644 src/de/steamwar/sql/SteamwarUser.java create mode 100644 src/de/steamwar/sql/UserGroup.java create mode 100644 src/de/steamwar/sql/internal/Field.java create mode 100644 src/de/steamwar/sql/internal/SQLConfig.java rename src/de/steamwar/sql/{ => internal}/SelectStatement.java (66%) rename src/de/steamwar/sql/{ => internal}/SqlTypeMapper.java (55%) rename src/de/steamwar/sql/{ => internal}/Statement.java (82%) rename src/de/steamwar/sql/{ => internal}/Table.java (79%) diff --git a/src/de/steamwar/sql/BauweltMember.java b/src/de/steamwar/sql/BauweltMember.java new file mode 100644 index 0000000..aa3ed93 --- /dev/null +++ b/src/de/steamwar/sql/BauweltMember.java @@ -0,0 +1,79 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Table; +import lombok.Getter; + +import java.util.*; + +public class BauweltMember { + private static final Map memberCache = new HashMap<>(); + + public static void clear() { + memberCache.clear(); + } + + private static final Table table = new Table<>(BauweltMember.class); + private static final SelectStatement getMember = table.select(Table.PRIMARY); + private static final SelectStatement getMembers = table.selectFields("BauweltID"); + + public static BauweltMember getBauMember(UUID ownerID, UUID memberID){ + return getBauMember(SteamwarUser.get(ownerID).getId(), SteamwarUser.get(memberID).getId()); + } + + public static BauweltMember getBauMember(int ownerID, int memberID){ + BauweltMember member = memberCache.get(memberID); + if(member != null) + return member; + return getMember.select(ownerID, memberID); + } + + public static List getMembers(UUID bauweltID){ + return getMembers(SteamwarUser.get(bauweltID).getId()); + } + + public static List getMembers(int bauweltID){ + return getMembers.listSelect(bauweltID); + } + + @Getter + @Field(keys = {Table.PRIMARY}) + private final int bauweltID; + @Getter + @Field(keys = {Table.PRIMARY}) + private final int memberID; + @Getter + @Field + private final boolean worldEdit; + @Getter + @Field + private final boolean world; + + public BauweltMember(int bauweltID, int memberID, boolean worldEdit, boolean world) { + this.bauweltID = bauweltID; + this.memberID = memberID; + this.worldEdit = worldEdit; + this.world = world; + memberCache.put(memberID, this); + } +} diff --git a/src/de/steamwar/sql/Event.java b/src/de/steamwar/sql/Event.java new file mode 100644 index 0000000..b3f8f8c --- /dev/null +++ b/src/de/steamwar/sql/Event.java @@ -0,0 +1,72 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.sql.Timestamp; + +@AllArgsConstructor +public class Event { + + private static final Table table = new Table<>(Event.class); + private static final SelectStatement byId = table.select(Table.PRIMARY); + + public static Event get(int eventID){ + return byId.select(eventID); + } + + @Getter + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int eventID; + @Getter + @Field(keys = {"eventName"}) + private final String eventName; + @Getter + @Field + private final Timestamp deadline; + @Getter + @Field + private final Timestamp start; + @Getter + @Field + private final Timestamp end; + @Getter + @Field + private final int maximumTeamMembers; + @Getter + @Field(nullable = true) + private final SchematicType schematicType; + @Field + private final boolean publicSchemsOnly; + @Field + private final boolean spectateSystem; + + public boolean publicSchemsOnly() { + return publicSchemsOnly; + } + public boolean spectateSystem(){ + return spectateSystem; + } +} diff --git a/src/de/steamwar/sql/EventFight.java b/src/de/steamwar/sql/EventFight.java new file mode 100644 index 0000000..60e4831 --- /dev/null +++ b/src/de/steamwar/sql/EventFight.java @@ -0,0 +1,72 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class EventFight { + + private static final Table table = new Table<>(EventFight.class); + private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final Statement setResult = table.update(Table.PRIMARY, "Ergebnis"); + private static final Statement setFight = table.update(Table.PRIMARY, "Fight"); + + public static EventFight get(int fightID) { + return byId.select(fightID); + } + + @Getter + @Field + private final int eventID; + @Getter + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int fightID; + @Getter + @Field + private final int teamBlue; + @Getter + @Field + private final int teamRed; + @Getter + @Field + private final int kampfleiter; + @Getter + @Field(def = "0") + private int ergebnis; + @Field(nullable = true) + private int fight; + + public void setErgebnis(int winner) { + this.ergebnis = winner; + setResult.update(winner, fightID); + } + + public void setFight(int fight) { + //Fight.FightID, not EventFight.FightID + this.fight = fight; + setFight.update(fight, fightID); + } +} diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java deleted file mode 100644 index 656fdd9..0000000 --- a/src/de/steamwar/sql/Field.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2022 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.sql; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -public @interface Field { - String[] keys() default {}; - String def() default ""; - boolean nullable() default false; - boolean autoincrement() default false; -} diff --git a/src/de/steamwar/sql/Fight.java b/src/de/steamwar/sql/Fight.java new file mode 100644 index 0000000..0f9eae6 --- /dev/null +++ b/src/de/steamwar/sql/Fight.java @@ -0,0 +1,61 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.sql.Timestamp; + +@AllArgsConstructor +public class Fight { + + private static final Table table = new Table<>(Fight.class); + private static final Statement insert = table.insertFields(true, "GameMode", "Server", "StartTime", "Duration", "BlueLeader", "RedLeader", "BlueSchem", "RedSchem", "Win", "WinCondition"); + + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int fightID; + @Field + private final String gameMode; + @Field + private final String server; + @Field + private final Timestamp startTime; + @Field + private final int duration; + @Field + private final int blueLeader; + @Field + private final int redLeader; + @Field(nullable = true) + private final Integer blueSchem; + @Field(nullable = true) + private final Integer redSchem; + @Field + private final int win; + @Field + private final String wincondition; + + public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ + return insert.insertGetKey(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition); + } +} diff --git a/src/de/steamwar/sql/FightPlayer.java b/src/de/steamwar/sql/FightPlayer.java new file mode 100644 index 0000000..cc529b8 --- /dev/null +++ b/src/de/steamwar/sql/FightPlayer.java @@ -0,0 +1,49 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class FightPlayer { + + private static final Table table = new Table<>(FightPlayer.class); + private static final Statement create = table.insertAll(); + + @Field(keys = {Table.PRIMARY}) + private final int fightID; + @Field(keys = {Table.PRIMARY}) + private final int userID; + @Field + private final int team; + @Field + private final String kit; + @Field + private final int kills; + @Field + private final boolean isOut; + + public static void create(int fightID, int userID, boolean blue, String kit, int kills, boolean isOut) { + create.update(fightID, userID, blue ? 1 : 2, kit, kills, isOut); + } +} diff --git a/src/de/steamwar/sql/NoClipboardException.java b/src/de/steamwar/sql/NoClipboardException.java new file mode 100644 index 0000000..9743348 --- /dev/null +++ b/src/de/steamwar/sql/NoClipboardException.java @@ -0,0 +1,23 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +public class NoClipboardException extends RuntimeException { +} diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java new file mode 100644 index 0000000..f0de53c --- /dev/null +++ b/src/de/steamwar/sql/NodeMember.java @@ -0,0 +1,69 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.HashSet; +import java.util.Set; + +@AllArgsConstructor +public class NodeMember { + + private static final Table table = new Table<>(NodeMember.class); + private static final SelectStatement getNodeMember = table.select(Table.PRIMARY); + private static final SelectStatement getNodeMembers = table.selectFields("Node"); + private static final SelectStatement getSchematics = table.selectFields("Member"); + private static final Statement create = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + @Getter + @Field(keys = {Table.PRIMARY}) + private final int node; + @Getter + @Field(keys = {Table.PRIMARY}) + private final int member; + + public void delete() { + delete.update(node, member); + } + + public static NodeMember createNodeMember(int node, int member) { + create.update(node, member); + return new NodeMember(node, member); + } + + public static NodeMember getNodeMember(int node, int member) { + return getNodeMember.select(node, member); + } + + public static Set getNodeMembers(int node) { + return new HashSet<>(getNodeMembers.listSelect(node)); + } + + public static Set getSchematics(int member) { + return new HashSet<>(getSchematics.listSelect(member)); + } +} diff --git a/src/de/steamwar/sql/Replay.java b/src/de/steamwar/sql/Replay.java new file mode 100644 index 0000000..90d2b34 --- /dev/null +++ b/src/de/steamwar/sql/Replay.java @@ -0,0 +1,73 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.sql.SQLException; + +@AllArgsConstructor +public class Replay { + + static { + new SqlTypeMapper<>(File.class, "BLOB", (rs, identifier) -> { + try { + File file = File.createTempFile("replay", "replay"); + file.deleteOnExit(); + Files.copy(rs.getBinaryStream(identifier), file.toPath()); + return file; + } catch (IOException e) { + throw new SQLException(e); + } + }, (st, index, value) -> { + try { + st.setBinaryStream(index, new FileInputStream(value)); + } catch (FileNotFoundException e) { + throw new SQLException(e); + } + }); + } + + private static final Table table = new Table<>(Replay.class); + private static final SelectStatement get = table.select(Table.PRIMARY); + + public static final Statement insert = table.insertAll(); + + public static Replay get(int fightID) { + return get.select(fightID); + } + + public static void save(int fightID, File file) { + insert.update(fightID, file); + } + + @Field(keys = {Table.PRIMARY}) + private final int fightID; + @Getter + @Field + private final File replay; +} diff --git a/src/de/steamwar/sql/SQLConfig.java b/src/de/steamwar/sql/SQLConfig.java deleted file mode 100644 index 2420a0e..0000000 --- a/src/de/steamwar/sql/SQLConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2022 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.sql; - -import de.steamwar.ImplementationProvider; - -import java.util.logging.Logger; - -public interface SQLConfig { - SQLConfig impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLConfigImpl"); - - Logger getLogger(); - - int maxConnections(); - - -} diff --git a/src/de/steamwar/sql/SQLWrapper.java b/src/de/steamwar/sql/SQLWrapper.java new file mode 100644 index 0000000..81abe61 --- /dev/null +++ b/src/de/steamwar/sql/SQLWrapper.java @@ -0,0 +1,31 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.ImplementationProvider; + +import java.util.List; +import java.util.Map; + +public interface SQLWrapper { + SQLWrapper impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl"); + + void loadSchemTypes(List tmpTypes, Map tmpFromDB); +} diff --git a/src/de/steamwar/sql/SchematicType.java b/src/de/steamwar/sql/SchematicType.java new file mode 100644 index 0000000..56c46ad --- /dev/null +++ b/src/de/steamwar/sql/SchematicType.java @@ -0,0 +1,116 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.SqlTypeMapper; + +import java.util.*; + +public class SchematicType { + + public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON"); + + private static final Map fromDB; + private static final List types; + + static { + List tmpTypes = new LinkedList<>(); + Map tmpFromDB = new HashMap<>(); + + tmpTypes.add(Normal); + tmpFromDB.put(Normal.name().toLowerCase(), Normal); + + SQLWrapper.impl.loadSchemTypes(tmpTypes, tmpFromDB); + + fromDB = Collections.unmodifiableMap(tmpFromDB); + types = Collections.unmodifiableList(tmpTypes); + } + + static { + new SqlTypeMapper<>(SchematicType.class, "VARCHAR(16)", (rs, identifier) -> { + String t = rs.getString(identifier); + return t != null ? fromDB.get(t) : null; + }, (st, index, value) -> st.setString(index, value.toDB())); + } + + private final String name; + private final String kuerzel; + private final Type type; + private final SchematicType checkType; + private final String material; + + SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material){ + this.name = name; + this.kuerzel = kuerzel; + this.type = type; + this.checkType = checkType; + this.material = material; + } + + public boolean isAssignable(){ + return type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null); + } + + public SchematicType checkType(){ + return checkType; + } + + public boolean check(){ + return type == Type.CHECK_TYPE; + } + + public boolean fightType(){ + return type == Type.FIGHT_TYPE; + } + + public boolean writeable(){ + return type == Type.NORMAL; + } + + public String name(){ + return name; + } + + public String getKuerzel() { + return kuerzel; + } + + public String getMaterial() { + return material; + } + + public String toDB(){ + return name.toLowerCase(); + } + + public static SchematicType fromDB(String input){ + return fromDB.getOrDefault(input.toLowerCase(), null); + } + + public static List values(){ + return types; + } + + enum Type{ + NORMAL, + CHECK_TYPE, + FIGHT_TYPE + } +} diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java new file mode 100644 index 0000000..7245db7 --- /dev/null +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -0,0 +1,153 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.Getter; + +import java.util.*; + +public class SteamwarUser { + + static { + new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); + new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> { + String l = rs.getString(identifier); + return l != null ? Locale.forLanguageTag(l) : null; + }, (st, index, value) -> st.setString(index, value.toLanguageTag())); + SqlTypeMapper.nameEnumMapper(UserGroup.class); + new SqlTypeMapper<>(SteamwarUser.class, null, (rs, identifier) -> { throw new SecurityException("SteamwarUser cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.id)); + } + + private static final Table table = new Table<>(SteamwarUser.class, "UserData"); + private static final Statement insert = table.insertFields("UUID", "UserName"); + private static final SelectStatement byID = table.selectFields("id"); + private static final SelectStatement byUUID = table.selectFields("UUID"); + private static final SelectStatement byName = table.selectFields("UserName"); + private static final SelectStatement byDiscord = table.selectFields("DiscordId"); + private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); + private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); + private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale"); + private static final Statement updateTeam = table.update(Table.PRIMARY, "Team"); + private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader"); + private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId"); + + private static final Map usersById = new HashMap<>(); + private static final Map usersByUUID = new HashMap<>(); + private static final Map usersByName = new HashMap<>(); + private static final Map usersByDiscord = new HashMap<>(); + public static void clear() { + usersById.clear(); + usersByName.clear(); + usersByUUID.clear(); + usersByDiscord.clear(); + } + + public static void invalidate(int userId) { + SteamwarUser user = usersById.remove(userId); + if (user == null) + return; + usersByName.remove(user.getUserName()); + usersByUUID.remove(user.getUUID()); + } + + @Getter + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int id; + @Field(keys = {"uuid"}) + private final UUID uuid; + @Getter + @Field + private String userName; + @Getter + @Field(def = "'Member'") + private final UserGroup userGroup; + @Getter + @Field(def = "0") + private int team; + @Field(def = "0") + private boolean leader; + @Field(nullable = true) + private Locale locale; + @Field(def = "0") + private boolean manualLocale; + @Field(keys = {"discordId"}, nullable = true) + private Long discordId; + + public SteamwarUser(int id, UUID uuid, String userName, UserGroup userGroup, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { + this.id = id; + this.uuid = uuid; + this.userName = userName; + this.userGroup = userGroup; + this.team = team; + this.leader = leader; + this.locale = locale; + this.manualLocale = manualLocale; + this.discordId = discordId != 0 ? discordId : null; + + usersById.put(id, this); + usersByName.put(userName.toLowerCase(), this); + usersByUUID.put(uuid, this); + if (this.discordId != null) { + usersByDiscord.put(discordId, this); + } + } + + public UUID getUUID() { + return uuid; + } + + public Locale getLocale() { + if(locale != null) + return locale; + return Locale.getDefault(); //TODO test correct locale on join (z.B. FightSystem Hotbarkit) + } + + public static SteamwarUser get(String userName){ + SteamwarUser user = usersByName.get(userName.toLowerCase()); + if(user != null) + return user; + return byName.select(userName); + } + + public static SteamwarUser get(UUID uuid){ + SteamwarUser user = usersByUUID.get(uuid); + if(user != null) + return user; + return byUUID.select(uuid); + } + + public static SteamwarUser get(int id) { + SteamwarUser user = usersById.get(id); + if(user != null) + return user; + return byID.select(id); + } + + public static SteamwarUser get(Long discordId) { + if(usersByDiscord.containsKey(discordId)) + return usersByDiscord.get(discordId); + return byDiscord.select(discordId); + } + + public static List getServerTeam() { + return getServerTeam.listSelect(); + } +} diff --git a/src/de/steamwar/sql/UserGroup.java b/src/de/steamwar/sql/UserGroup.java new file mode 100644 index 0000000..cef4fee --- /dev/null +++ b/src/de/steamwar/sql/UserGroup.java @@ -0,0 +1,45 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum UserGroup { + Admin("§4", "§e", true, true, true), + Developer("§3", "§f", true, true, true), + Moderator("§c", "§f", true, true, true), + Supporter("§9", "§f", false, true, true), + Builder("§2", "§f", false, true, false), + YouTuber("§5", "§f", false, false, false), + Member("§7", "§7", false, false, false); + + @Getter + private final String colorCode; + @Getter + private final String chatColorCode; + @Getter + private final boolean adminGroup; + @Getter + private final boolean teamGroup; + @Getter + private final boolean checkSchematics; +} \ No newline at end of file diff --git a/src/de/steamwar/sql/internal/Field.java b/src/de/steamwar/sql/internal/Field.java new file mode 100644 index 0000000..90bfa1d --- /dev/null +++ b/src/de/steamwar/sql/internal/Field.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Field { + String[] keys() default {}; + String def() default ""; + boolean nullable() default false; + boolean autoincrement() default false; +} diff --git a/src/de/steamwar/sql/internal/SQLConfig.java b/src/de/steamwar/sql/internal/SQLConfig.java new file mode 100644 index 0000000..3153ecb --- /dev/null +++ b/src/de/steamwar/sql/internal/SQLConfig.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql.internal; + +import de.steamwar.ImplementationProvider; + +import java.util.logging.Logger; + +public interface SQLConfig { + SQLConfig impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLConfigImpl"); + + Logger getLogger(); + + int maxConnections(); + + +} diff --git a/src/de/steamwar/sql/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java similarity index 66% rename from src/de/steamwar/sql/SelectStatement.java rename to src/de/steamwar/sql/internal/SelectStatement.java index cc00ecc..0f4a329 100644 --- a/src/de/steamwar/sql/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -1,23 +1,23 @@ /* - * This file is a part of the SteamWar software. + * This file is a part of the SteamWar software. * - * Copyright (C) 2022 SteamWar.de-Serverteam + * Copyright (C) 2022 SteamWar.de-Serverteam * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.lang.reflect.InvocationTargetException; import java.sql.ResultSet; diff --git a/src/de/steamwar/sql/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java similarity index 55% rename from src/de/steamwar/sql/SqlTypeMapper.java rename to src/de/steamwar/sql/internal/SqlTypeMapper.java index 68d2ee8..d364bc4 100644 --- a/src/de/steamwar/sql/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -1,23 +1,23 @@ /* - * This file is a part of the SteamWar software. + * This file is a part of the SteamWar software. * - * Copyright (C) 2022 SteamWar.de-Serverteam + * Copyright (C) 2022 SteamWar.de-Serverteam * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -34,18 +34,6 @@ public final class SqlTypeMapper { return (SqlTypeMapper) mappers.get(clazz); } - static { - new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString); - new SqlTypeMapper<>(Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); - new SqlTypeMapper<>(Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); - new SqlTypeMapper<>(Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); - new SqlTypeMapper<>(Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); - new SqlTypeMapper<>(Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); - new SqlTypeMapper<>(Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); - new SqlTypeMapper<>(Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); - new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp); - } - public static > void ordinalEnumMapper(Class type) { T[] enumConstants = type.getEnumConstants(); new SqlTypeMapper<>( @@ -65,6 +53,26 @@ public final class SqlTypeMapper { ); } + static { + primitiveMapper(boolean.class, Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); + primitiveMapper(byte.class, Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); + primitiveMapper(short.class, Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); + primitiveMapper(int.class, Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); + primitiveMapper(long.class, Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); + primitiveMapper(float.class, Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); + primitiveMapper(double.class, Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); + new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString); + new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp); + } + + private static void primitiveMapper(Class primitive, Class wrapped, String sqlType, SQLReader reader, SQLWriter writer) { + new SqlTypeMapper<>(primitive, sqlType, reader, writer); + new SqlTypeMapper<>(wrapped, sqlType, (rs, identifier) -> { + T value = reader.read(rs, identifier); + return rs.wasNull() ? null : value; + }, writer); + } + private final String sqlType; private final SQLReader reader; private final SQLWriter writer; diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/internal/Statement.java similarity index 82% rename from src/de/steamwar/sql/Statement.java rename to src/de/steamwar/sql/internal/Statement.java index 9626f0e..2e9edb8 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -1,23 +1,23 @@ /* - This file is a part of the SteamWar software. - - Copyright (C) 2022 SteamWar.de-Serverteam - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.io.File; import java.io.FileReader; @@ -92,11 +92,17 @@ public class Statement implements AutoCloseable { } } + private final boolean returnGeneratedKeys; private final String sql; private final Map cachedStatements = new HashMap<>(); public Statement(String sql) { + this(sql, false); + } + + public Statement(String sql, boolean returnGeneratedKeys) { this.sql = sql; + this.returnGeneratedKeys = returnGeneratedKeys; synchronized (statements) { statements.add(this); } @@ -115,6 +121,15 @@ public class Statement implements AutoCloseable { withConnection(PreparedStatement::executeUpdate, objects); } + public int insertGetKey(Object... objects) { + return withConnection(st -> { + st.executeUpdate(); + ResultSet rs = st.getGeneratedKeys(); + rs.next(); + return rs.getInt(1); + }, objects); + } + public String getSql() { return sql; } @@ -150,7 +165,10 @@ public class Statement implements AutoCloseable { private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { PreparedStatement st = cachedStatements.get(connection); if(st == null) { - st = connection.prepareStatement(sql); + if(returnGeneratedKeys) + st = connection.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS); + else + st = connection.prepareStatement(sql); cachedStatements.put(connection, st); } diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/internal/Table.java similarity index 79% rename from src/de/steamwar/sql/Table.java rename to src/de/steamwar/sql/internal/Table.java index 74dfc83..ffef01c 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -1,23 +1,23 @@ /* - * This file is a part of the SteamWar software. + * This file is a part of the SteamWar software. * - * Copyright (C) 2022 SteamWar.de-Serverteam + * Copyright (C) 2022 SteamWar.de-Serverteam * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.lang.reflect.Constructor; import java.sql.ResultSet; @@ -83,16 +83,24 @@ public class Table { return insertFields(keyFields(name)); } + public Statement insertAll() { + return insertFields(false, Arrays.stream(fields).map(f -> f.identifier).toArray(String[]::new)); + } + public Statement insertFields(String... fields) { + return insertFields(false, fields); + } + + public Statement insertFields(boolean returnGeneratedKeys, String... fields) { List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f).field.keys().length == 0).collect(Collectors.toList()); - return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", ")))); + return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", "))), returnGeneratedKeys); } - public Statement deleteWithKey(String name) { - return delete(keyFields(name)); + public Statement delete(String name) { + return deleteFields(keyFields(name)); } - public Statement delete(String... kfields) { + public Statement deleteFields(String... kfields) { return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); } -- 2.39.2 From 1758fc4297da23f6cae2e69115b510a6fb7d4aeb Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 20 Sep 2022 16:41:07 +0200 Subject: [PATCH 12/16] Full CommonDB untested --- src/de/steamwar/sql/CheckedSchematic.java | 71 +++ src/de/steamwar/sql/NodeDownload.java | 69 +++ src/de/steamwar/sql/Punishment.java | 119 +++++ src/de/steamwar/sql/SQLWrapper.java | 2 + src/de/steamwar/sql/SWException.java | 52 ++ src/de/steamwar/sql/SchemElo.java | 43 ++ src/de/steamwar/sql/SchematicNode.java | 543 ++++++++++++++++++++ src/de/steamwar/sql/SchematicType.java | 2 +- src/de/steamwar/sql/Season.java | 54 ++ src/de/steamwar/sql/SteamwarUser.java | 9 + src/de/steamwar/sql/Team.java | 61 +++ src/de/steamwar/sql/TeamTeilnahme.java | 54 ++ src/de/steamwar/sql/UserConfig.java | 72 +++ src/de/steamwar/sql/internal/Statement.java | 18 +- src/de/steamwar/sql/internal/Table.java | 3 +- 15 files changed, 1167 insertions(+), 5 deletions(-) create mode 100644 src/de/steamwar/sql/CheckedSchematic.java create mode 100644 src/de/steamwar/sql/NodeDownload.java create mode 100644 src/de/steamwar/sql/Punishment.java create mode 100644 src/de/steamwar/sql/SWException.java create mode 100644 src/de/steamwar/sql/SchemElo.java create mode 100644 src/de/steamwar/sql/SchematicNode.java create mode 100644 src/de/steamwar/sql/Season.java create mode 100644 src/de/steamwar/sql/Team.java create mode 100644 src/de/steamwar/sql/TeamTeilnahme.java create mode 100644 src/de/steamwar/sql/UserConfig.java diff --git a/src/de/steamwar/sql/CheckedSchematic.java b/src/de/steamwar/sql/CheckedSchematic.java new file mode 100644 index 0000000..71fe24a --- /dev/null +++ b/src/de/steamwar/sql/CheckedSchematic.java @@ -0,0 +1,71 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.sql.Timestamp; +import java.util.List; + +@AllArgsConstructor +public class CheckedSchematic { + + private static final Table table = new Table<>(CheckedSchematic.class); + private static final SelectStatement statusOfNode = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC"); + + public static List getLastDeclinedOfNode(int node){ + return statusOfNode.listSelect(node); + } + + @Field(nullable = true) + private final Integer nodeId; + @Field + private final int nodeOwner; + @Field + private final String nodeName; + @Getter + @Field + private final int validator; + @Getter + @Field + private final Timestamp startTime; + @Getter + @Field + private final Timestamp endTime; + @Getter + @Field + private final String declineReason; + + public int getNode() { + return nodeId; + } + + public String getSchemName() { + return nodeName; + } + + public int getSchemOwner() { + return nodeOwner; + } +} diff --git a/src/de/steamwar/sql/NodeDownload.java b/src/de/steamwar/sql/NodeDownload.java new file mode 100644 index 0000000..0984096 --- /dev/null +++ b/src/de/steamwar/sql/NodeDownload.java @@ -0,0 +1,69 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Timestamp; +import java.time.Instant; + +@AllArgsConstructor +public class NodeDownload { + + private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + private static final String LINK_BASE = "https://steamwar.de/download.php?schem="; + + private static final Table table = new Table<>(NodeDownload.class); + private static final Statement insert = table.insertFields("NodeId", "Link"); + + @Field(keys = {Table.PRIMARY}) + private final int nodeId; + @Field + private final String link; + @Field(def = "CURRENT_TIMESTAMP") + private final Timestamp timestamp; + + public static String getLink(SchematicNode schem){ + if(schem.isDir()) + throw new SecurityException("Can not Download Directorys"); + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new SecurityException(e); + } + digest.reset(); + digest.update((Instant.now().toString() + schem.getOwner() + schem.getId()).getBytes()); + String hash = base16encode(digest.digest()); + insert.update(schem.getId(), hash); + return LINK_BASE + hash; + } + public static String base16encode(byte[] byteArray) { + StringBuilder hexBuffer = new StringBuilder(byteArray.length * 2); + for (byte b : byteArray) + hexBuffer.append(HEX[b >> 4]).append(HEX[b & 0xF]); + return hexBuffer.toString(); + } +} diff --git a/src/de/steamwar/sql/Punishment.java b/src/de/steamwar/sql/Punishment.java new file mode 100644 index 0000000..22f54ce --- /dev/null +++ b/src/de/steamwar/sql/Punishment.java @@ -0,0 +1,119 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.SqlTypeMapper; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.sql.Timestamp; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@AllArgsConstructor +public class Punishment { + + static { + SqlTypeMapper.nameEnumMapper(PunishmentType.class); + } + + private static final Table table = new Table<>(Punishment.class, "Punishments"); + private static final SelectStatement getPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE PunishmentId IN (SELECT MAX(PunishmentId) FROM Punishments WHERE UserId = ? GROUP BY Type)"); + private static final SelectStatement getPunishment = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? AND Type = ? ORDER BY PunishmentId DESC LIMIT 1"); + + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int punishmentId; + @Field + @Getter + private final int user; + @Field + @Getter + private final int punisher; + @Field + @Getter + private final PunishmentType type; + @Field + @Getter + private final Timestamp startTime; + @Field + @Getter + private final Timestamp endTime; + @Field + @Getter + private final boolean perma; + @Field + @Getter + private final String reason; + + public static Punishment getPunishmentOfPlayer(int user, PunishmentType type) { + return getPunishment.select(user, type); + } + + public static Map getPunishmentsOfPlayer(int user) { + return getPunishments.listSelect(user).stream().collect(Collectors.toMap(Punishment::getType, punishment -> punishment)); + } + + public static boolean isPunished(SteamwarUser user, Punishment.PunishmentType type, Consumer callback) { + Punishment punishment = Punishment.getPunishmentOfPlayer(user.getId(), type); + if(punishment == null || !punishment.isCurrent()) { + return false; + } else { + callback.accept(punishment); + return true; + } + } + + @Deprecated // Not multiling, misleading title + public String getBantime(Timestamp endTime, boolean perma) { + if (perma) { + return "permanent"; + } else { + return endTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")); + } + } + + public boolean isCurrent() { + return isPerma() || getEndTime().after(new Date()); + } + + @AllArgsConstructor + @Getter + public enum PunishmentType { + Ban(false, "BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN"), + Mute( false, "MUTE_TEAM", "MUTE_PERMA", "MUTE_UNTIL", "UNMUTE_ERROR", "UNMUTE"), + NoSchemReceiving(false, "NOSCHEMRECEIVING_TEAM", "NOSCHEMRECEIVING_PERMA", "NOSCHEMRECEIVING_UNTIL", "UNNOSCHEMRECEIVING_ERROR", "UNNOSCHEMRECEIVING"), + NoSchemSharing(false, "NOSCHEMSHARING_TEAM", "NOSCHEMSHARING_PERMA", "NOSCHEMSHARING_UNTIL", "UNNOSCHEMSHARING_ERROR", "UNNOSCHEMSHARING"), + NoSchemSubmitting(true, "NOSCHEMSUBMITTING_TEAM", "NOSCHEMSUBMITTING_PERMA", "NOSCHEMSUBMITTING_UNTIL", "UNNOSCHEMSUBMITTING_ERROR", "UNNOSCHEMSUBMITTING"), + NoDevServer(true, "NODEVSERVER_TEAM", "NODEVSERVER_PERMA", "NODEVSERVER_UNTIL", "UNNODEVSERVER_ERROR", "UNNODEVSERVER"); + + private final boolean needsAdmin; + private final String teamMessage; + private final String playerMessagePerma; + private final String playerMessageUntil; + private final String usageNotPunished; + private final String unpunishmentMessage; + } +} diff --git a/src/de/steamwar/sql/SQLWrapper.java b/src/de/steamwar/sql/SQLWrapper.java index 81abe61..cff7dec 100644 --- a/src/de/steamwar/sql/SQLWrapper.java +++ b/src/de/steamwar/sql/SQLWrapper.java @@ -28,4 +28,6 @@ public interface SQLWrapper { SQLWrapper impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl"); void loadSchemTypes(List tmpTypes, Map tmpFromDB); + + void additionalExceptionMetadata(StringBuilder builder); } diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java new file mode 100644 index 0000000..af1f9c1 --- /dev/null +++ b/src/de/steamwar/sql/SWException.java @@ -0,0 +1,52 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.io.File; + +@AllArgsConstructor +public class SWException { + + private static final String CWD = System.getProperty("user.dir"); + private static final String SERVER_NAME = new File(CWD).getName(); + + private static final Table table = new Table<>(SWException.class, "Exception"); + private static final Statement insert = table.insertAll(); + + @Field + private final String server; + @Field + private final String message; + @Field + private final String stacktrace; + + public static void log(String message, String stacktrace){ + StringBuilder msgBuilder = new StringBuilder(message); + SQLWrapper.impl.additionalExceptionMetadata(msgBuilder); + msgBuilder.append("\nCWD: ").append(CWD); + + insert.update(SERVER_NAME, msgBuilder.toString(), stacktrace); + } +} diff --git a/src/de/steamwar/sql/SchemElo.java b/src/de/steamwar/sql/SchemElo.java new file mode 100644 index 0000000..3aedaba --- /dev/null +++ b/src/de/steamwar/sql/SchemElo.java @@ -0,0 +1,43 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class SchemElo { + + private static final Table table = new Table<>(SchemElo.class); + private static final SelectStatement select = table.select(Table.PRIMARY); + + @Field(keys = {Table.PRIMARY}) + private final int schemId; + @Field + private final int elo; + @Field(keys = {Table.PRIMARY}) + private final int season; + + public static int getElo(SchematicNode node, int season) { + return select.select(node, season).elo; + } +} diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java new file mode 100644 index 0000000..cc62acc --- /dev/null +++ b/src/de/steamwar/sql/SchematicNode.java @@ -0,0 +1,543 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.AccessLevel; +import lombok.Setter; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class SchematicNode { + + static { + new SqlTypeMapper<>(SchematicNode.class, null, (rs, identifier) -> { throw new SecurityException("SchematicNode cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.nodeId)); + } + + private static final Map>> TAB_CACHE = new HashMap<>(); + public static void clear() { + TAB_CACHE.clear(); + } + + private static final String[] fields = {"NodeId", "NodeOwner", "NodeName", "ParentNode", "LastUpdate", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"}; + private static String nodeSelectCreator(String itemPrefix) { + return "SELECT " + Arrays.stream(fields).map(s -> itemPrefix + s).collect(Collectors.joining(", ")) + " FROM SchematicNode "; + } + + private static final Table table = new Table<>(SchematicNode.class); + private static final Statement create = table.insertFields(true, "NodeOwner", "NodeName", "ParentNode", "NodeItem", "NodeType"); + private static final Statement update = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement byOwnerNameParent = table.select("OwnerNameParent"); + private static final SelectStatement byOwnerNameParent_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode is NULL"); + private static final SelectStatement byParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode = ? ORDER BY NodeName"); + private static final SelectStatement byParent_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode is NULL ORDER BY NodeName"); + private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode = ? AND NodeType is NULL ORDER BY NodeName"); + private static final SelectStatement dirsByParent_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode is NULL AND NodeType is NULL ORDER BY NodeName"); + private static final SelectStatement byParentName = table.selectFields("NodeName", "ParentNode"); + private static final SelectStatement byParentName_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeName = ? AND ParentNode is NULL"); + private static final SelectStatement accessibleByUserTypeParent = new SelectStatement<>(table, "WITH RECURSIVE RSNB AS (WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN WHERE NodeType = ? UNION " + nodeSelectCreator("SN.") + "AS SN, RSNB WHERE SN.NodeId = RSNB.ParentNode)SELECT * FROM RSNB WHERE ParentNode = ? ORDER BY NodeName"); + private static final SelectStatement accessibleByUserTypeParent_Null = new SelectStatement<>(table, "WITH RECURSIVE RSNB AS (WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN WHERE NodeType = ? UNION " + nodeSelectCreator("SN.") + "AS SN, RSNB WHERE SN.NodeId = RSNB.ParentNode)SELECT * FROM RSNB WHERE ParentNode is null ORDER BY NodeName"); + private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN WHERE NodeType = ? ORDER BY NodeName"); + private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); + private static final SelectStatement byType = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeType = ? ORDER BY NodeName"); + private static final SelectStatement accessibleByUser = new SelectStatement<>(table, nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) AND ((s.NodeOwner = ? AND s.ParentNode IS NULL) OR NOT s.NodeOwner = ?) GROUP BY s.NodeId ORDER BY s.NodeName"); + private static final Statement schematicAccessibleForUser = new Statement("WITH RECURSIVE RSN AS (" + nodeSelectCreator("") + "WHERE NodeId = ? UNION " + nodeSelectCreator("SN.") + "SN, RSN WHERE RSN.ParentNode = SN.NodeId) SELECT COUNT(RSN.NodeId) AS `Accessible` FROM RSN LEFT Join NodeMember NM On NM.NodeId = RSN.NodeId WHERE NodeOwner = ? OR UserId = ? LIMIT 1"); + private static final SelectStatement allAccessibleByUser = new SelectStatement<>(table, "WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN ORDER BY NodeName"); + private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (" + nodeSelectCreator("") + "WHERE NodeId = ? UNION " + nodeSelectCreator("SN.") + "SN, RSN WHERE RSN.ParentNode = SN.NodeId) SELECT * FROM RSN ORDER BY NodeName"); + + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int nodeId; + @Field(keys = {"OwnerNameParent"}) + private final int nodeOwner; + @Field(keys = {"OwnerNameParent"}) + private String nodeName; + @Field(keys = {"OwnerNameParent"}, nullable = true) + private Integer parentNode; + @Field(def = "CURRENT_TIMESTAMP") + private Timestamp lastUpdate; + @Field(def = "''") + private String nodeItem; + @Field(def = "'normal'", nullable = true) + private SchematicType nodeType; + @Field(def = "0") + private int nodeRank; + @Field(def = "1") + private boolean replaceColor; + @Field(def = "1") + private boolean allowReplay; + @Setter(AccessLevel.PACKAGE) + @Field(def = "1") + private boolean nodeFormat; + + private final Map brCache = new HashMap<>(); + + public SchematicNode( + int nodeId, + int nodeOwner, + String nodeName, + Integer parentNode, + Timestamp lastUpdate, + String nodeItem, + SchematicType nodeType, + int nodeRank, + boolean replaceColor, + boolean allowReplay, + boolean nodeFormat + ) { + this.nodeId = nodeId; + this.nodeOwner = nodeOwner; + this.nodeName = nodeName; + this.parentNode = parentNode; + this.nodeItem = nodeItem; + this.nodeType = nodeType; + this.lastUpdate = lastUpdate; + this.nodeRank = nodeRank; + this.replaceColor = replaceColor; + this.allowReplay = allowReplay; + this.nodeFormat = nodeFormat; + } + + public static SchematicNode createSchematic(int owner, String name, Integer parent) { + return createSchematicNode(owner, name, parent, SchematicType.Normal.toDB(), ""); + } + + public static SchematicNode createSchematicDirectory(int owner, String name, Integer parent) { + return createSchematicNode(owner, name, parent, null, ""); + } + + public static SchematicNode createSchematicNode(int owner, String name, Integer parent, String type, String item) { + if (parent != null && parent == 0) + parent = null; + int nodeId = create.insertGetKey(owner, name, parent, type, item); + return getSchematicNode(nodeId); + } + + public static SchematicNode getSchematicNode(int owner, String name, SchematicNode parent) { + return getSchematicNode(owner, name, parent.getId()); + } + + public static SchematicNode getSchematicNode(int owner, String name, Integer parent) { + if (parent == null || parent == 0) + return byOwnerNameParent_null.select(owner, name); + return byOwnerNameParent.select(owner, name, parent); + } + + public static List getSchematicNodeInNode(SchematicNode parent) { + return getSchematicNodeInNode(parent.getId()); + } + + public static List getSchematicNodeInNode(Integer parent) { + if(parent == null || parent == 0) { + rootWarning(); + return byParent_null.listSelect(); + } + + return byParent.listSelect(parent); + } + + public static List getSchematicDirectoryInNode(Integer parent) { + if(parent == null || parent == 0) { + rootWarning(); + return dirsByParent_null.listSelect(); + } + return dirsByParent.listSelect(parent); + } + + @Deprecated + public static SchematicNode getSchematicDirectory(String name, SchematicNode parent) { + return getSchematicNode(name, parent.getId()); + } + + @Deprecated + public static SchematicNode getSchematicDirectory(String name, Integer parent) { + return getSchematicNode(name, parent); + } + + public static SchematicNode getSchematicNode(String name, Integer parent) { + if(parent == null || parent == 0) { + rootWarning(); + return byParentName_null.select(name); + } + return byParentName.select(name, parent); + } + + public static SchematicNode getSchematicNode(int id) { + return byId.select(id); + } + + public static List getAccessibleSchematicsOfTypeInParent(int owner, String schemType, Integer parent) { + if(parent == null || parent == 0) + return accessibleByUserTypeParent_Null.listSelect(owner, owner, schemType); + return accessibleByUserTypeParent.listSelect(owner, owner, schemType, parent); + } + + public static List getAllAccessibleSchematicsOfType(int user, String schemType) { + return accessibleByUserType.listSelect(user, user, schemType); + } + + public static List getAllSchematicsOfType(int owner, String schemType) { + return byOwnerType.listSelect(owner, schemType); + } + + @Deprecated + public static List getAllSchematicsOfType(String schemType) { + return byType.listSelect(schemType); + } + + public static List getAllSchematicsOfType(SchematicType schemType) { + return byType.listSelect(schemType); + } + + public static List deepGet(Integer parent, Predicate filter) { + List finalList = new ArrayList<>(); + List nodes = SchematicNode.getSchematicNodeInNode(parent); + nodes.forEach(node -> { + if (node.isDir()) { + finalList.addAll(deepGet(node.getId(), filter)); + } else { + if (filter.test(node)) + finalList.add(node); + } + }); + return finalList; + } + + public static List getSchematicsAccessibleByUser(int user, Integer parent) { + if (parent == null || parent == 0) + return accessibleByUser.listSelect(user, user, user, user); + + if(schematicAccessibleForUser.select(rs -> { + rs.next(); + return rs.getInt("Accessible") > 0; + }, parent, user, user)) + return getSchematicNodeInNode(parent); + + return Collections.emptyList(); + } + + public static List getAllSchematicsAccessibleByUser(int user) { + return allAccessibleByUser.listSelect(user, user); + } + + public static List getAllParentsOfNode(SchematicNode node) { + return getAllParentsOfNode(node.getId()); + } + + public static List getAllParentsOfNode(int node) { + return allParentsOfNode.listSelect(node); + } + + public static SchematicNode getNodeFromPath(SteamwarUser user, String s) { + if (s.startsWith("/")) { + s = s.substring(1); + } + if (s.isEmpty()) { + return null; + } + if (s.contains("/")) { + String[] layers = s.split("/"); + SchematicNode currentNode = null; + for (int i = 0; i < layers.length; i++) { + int finalI = i; + Optional node; + if (currentNode == null) { + node = SchematicNode.getSchematicsAccessibleByUser(user.getId(), 0).stream().filter(node1 -> node1.getName().equals(layers[finalI])).findAny(); + } else { + node = Optional.ofNullable(SchematicNode.getSchematicNode(layers[i], currentNode.getId())); + } + if (!node.isPresent()) { + return null; + } else { + currentNode = node.get(); + if (!currentNode.isDir() && i != layers.length - 1) { + return null; + } + } + } + return currentNode; + } else { + String finalS = s; + return SchematicNode.getSchematicsAccessibleByUser(user.getId(), 0).stream().filter(node1 -> node1.getName().equals(finalS)).findAny().orElse(null); + } + } + + public static List filterSchems(int user, Predicate filter) { + List finalList = new ArrayList<>(); + List nodes = getSchematicsAccessibleByUser(user, null); + nodes.forEach(node -> { + if (node.isDir()) { + finalList.addAll(deepGet(node.getId(), filter)); + } else { + if (filter.test(node)) + finalList.add(node); + } + }); + return finalList; + } + + @Deprecated + public static Integer countNodes() { + return -1; + } + + public int getId() { + return nodeId; + } + + public int getOwner() { + return nodeOwner; + } + + public String getName() { + return nodeName; + } + + public void setName(String name) { + this.nodeName = name; + updateDB(); + } + + public Integer getParent() { + return parentNode; + } + + public void setParent(Integer parent) { + this.parentNode = parent; + updateDB(); + } + + public String getItem() { + if (nodeItem.isEmpty()) { + return isDir() ? "CHEST" : "CAULDRON_ITEM"; + } + return nodeItem; + } + + public void setItem(String item) { + this.nodeItem = item; + updateDB(); + } + + @Deprecated + public String getType() { + return nodeType.name(); + } + + @Deprecated + public void setType(String type) { + if(isDir()) + throw new SecurityException("Node is Directory"); + this.nodeType = SchematicType.fromDB(type); + updateDB(); + } + + public boolean isDir() { + return nodeType == null; + } + + public boolean getSchemFormat() { + if(isDir()) + throw new SecurityException("Node is Directory"); + return nodeFormat; + } + + public int getRank() { + if(isDir()) + throw new SecurityException("Node is Directory"); + return nodeRank; + } + + @Deprecated + public int getRankUnsafe() { + return nodeRank; + } + + public void setRank(int rank) { + if(isDir()) + throw new SecurityException("Node is Directory"); + this.nodeRank = rank; + } + + public SchematicType getSchemtype() { + if(isDir()) + throw new SecurityException("Is Directory"); + return nodeType; + } + + public void setSchemtype(SchematicType type) { + if(isDir()) + throw new SecurityException("Is Directory"); + this.nodeType = type; + updateDB(); + } + + public boolean replaceColor() { + return replaceColor; + } + + public void setReplaceColor(boolean replaceColor) { + if(isDir()) + throw new SecurityException("Is Directory"); + this.replaceColor = replaceColor; + updateDB(); + } + + public boolean allowReplay() { + return allowReplay; + } + + public void setAllowReplay(boolean allowReplay) { + if(isDir()) + throw new SecurityException("Is Directory"); + this.allowReplay = allowReplay; + updateDB(); + } + + public SchematicNode getParentNode() { + if(parentNode == null) return null; + return SchematicNode.getSchematicNode(parentNode); + } + + public int getElo(int season) { + return SchemElo.getElo(this, season); + } + + public boolean accessibleByUser(int user) { + return NodeMember.getNodeMember(nodeId, user) != null; + } + + public Set getMembers() { + return NodeMember.getNodeMembers(nodeId); + } + + public Timestamp getLastUpdate() { + return lastUpdate; + } + + private void updateDB() { + this.lastUpdate = Timestamp.from(Instant.now()); + update.update(nodeId, nodeOwner, nodeName, parentNode, nodeItem, nodeType, lastUpdate, nodeRank, replaceColor, allowReplay, nodeFormat); + this.brCache.clear(); + TAB_CACHE.clear(); + } + + public void delete() { + delete.update(nodeId); + } + + @Override + public int hashCode() { + return nodeId; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SchematicNode)) + return false; + + return ((SchematicNode) obj).getId() == nodeId; + } + + public String generateBreadcrumbs(SteamwarUser user) { + return brCache.computeIfAbsent(user.getId(), integer -> generateBreadcrumbs("/", user)); + } + + public String generateBreadcrumbs(String split, SteamwarUser user) { + StringBuilder builder = new StringBuilder(getName()); + SchematicNode currentNode = this; + if (currentNode.isDir()) builder.append("/"); + final Set nodeMembers = NodeMember.getSchematics(user.getId()); + AtomicInteger i = new AtomicInteger(); + i.set(currentNode.getId()); + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) { + currentNode = currentNode.getParentNode(); + i.set(currentNode.getId()); + builder.insert(0, split) + .insert(0, currentNode.getName()); + } + return builder.toString(); + } + + private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public")); + public static boolean invalidSchemName(String[] layers) { + for (String layer : layers) { + if (layer.isEmpty()) { + return true; + } + if (layer.contains("/") || + layer.contains("\\") || + layer.contains("<") || + layer.contains(">") || + layer.contains("^") || + layer.contains("°") || + layer.contains("'") || + layer.contains("\"") || + layer.contains(" ")) { + return true; + } + if(FORBIDDEN_NAMES.contains(layer.toLowerCase())) { + return true; + } + } + return false; + } + + public static List getNodeTabcomplete(SteamwarUser user, String s) { + boolean sws = s.startsWith("/"); + if (sws) { + s = s.substring(1); + } + int index = s.lastIndexOf("/"); + String cacheKey = index == -1 ? "" : s.substring(0, index); + if(TAB_CACHE.containsKey(user.getId()) && TAB_CACHE.get(user.getId()).containsKey(cacheKey)) { + return new ArrayList<>(TAB_CACHE.get(user.getId()).get(cacheKey)); + } + List list = new ArrayList<>(); + if (s.contains("/")) { + String preTab = s.substring(0, s.lastIndexOf("/") + 1); + SchematicNode pa = SchematicNode.getNodeFromPath(user, preTab); + if (pa == null) return Collections.emptyList(); + List nodes = SchematicNode.getSchematicNodeInNode(pa); + nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs(user))); + } else { + List nodes = SchematicNode.getSchematicsAccessibleByUser(user.getId(), 0); + nodes.forEach(node -> list.add((sws ? "/" : "") + node.getName() + (node.isDir() ? "/" : ""))); + } + list.remove("//copy"); + TAB_CACHE.computeIfAbsent(user.getId(), integer -> new HashMap<>()).putIfAbsent(cacheKey, list); + return list; + } + + private static void rootWarning() { + ByteArrayOutputStream stacktraceOutput = new ByteArrayOutputStream(); + new Throwable().printStackTrace(new PrintStream(stacktraceOutput)); + SWException.log("PERFORMANCE!!! Getting all/weird subset of schematic nodes with parent NULL", stacktraceOutput.toString()); + } +} diff --git a/src/de/steamwar/sql/SchematicType.java b/src/de/steamwar/sql/SchematicType.java index 56c46ad..d87065c 100644 --- a/src/de/steamwar/sql/SchematicType.java +++ b/src/de/steamwar/sql/SchematicType.java @@ -101,7 +101,7 @@ public class SchematicType { } public static SchematicType fromDB(String input){ - return fromDB.getOrDefault(input.toLowerCase(), null); + return fromDB.get(input.toLowerCase()); } public static List values(){ diff --git a/src/de/steamwar/sql/Season.java b/src/de/steamwar/sql/Season.java new file mode 100644 index 0000000..8768ad8 --- /dev/null +++ b/src/de/steamwar/sql/Season.java @@ -0,0 +1,54 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.util.Calendar; + +public class Season { + private Season() {} + + public static int getSeason() { + Calendar calendar = Calendar.getInstance(); + int yearIndex = calendar.get(Calendar.MONTH) / 4; + return (calendar.get(Calendar.YEAR) * 3 + yearIndex); + } + + public static String getSeasonStart() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) / 4 * 3 + 1) + "-1"; + } + + public static String convertSeasonToString(int season){ + if (season == -1) return ""; + int yearSeason = season % 3; + int year = (season - yearSeason) / 3; + return String.format("%d-%d", year, yearSeason); + } + + public static int convertSeasonToNumber(String season){ + if (season.isEmpty()) return -1; + String[] split = season.split("-"); + try { + return Integer.parseInt(split[0]) * 3 + Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + return -1; + } + } +} diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 7245db7..c5ccbd5 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -42,6 +42,7 @@ public class SteamwarUser { private static final SelectStatement byUUID = table.selectFields("UUID"); private static final SelectStatement byName = table.selectFields("UserName"); private static final SelectStatement byDiscord = table.selectFields("DiscordId"); + private static final SelectStatement byTeam = table.selectFields("Team"); private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale"); @@ -147,7 +148,15 @@ public class SteamwarUser { return byDiscord.select(discordId); } + public static void createOrUpdateUsername(UUID uuid, String userName) { + insert.update(uuid, userName); + } + public static List getServerTeam() { return getServerTeam.listSelect(); } + + public static List getTeam(int teamId) { + return byTeam.listSelect(teamId); + } } diff --git a/src/de/steamwar/sql/Team.java b/src/de/steamwar/sql/Team.java new file mode 100644 index 0000000..10e6892 --- /dev/null +++ b/src/de/steamwar/sql/Team.java @@ -0,0 +1,61 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor +public class Team { + + private static final Table table = new Table<>(Team.class); + private static final SelectStatement select = table.select(Table.PRIMARY); + + @Field(keys = {Table.PRIMARY}) + @Getter + private final int teamId; + @Field + @Getter + private final String teamKuerzel; + @Field + @Getter + private final String teamName; + @Field(def = "'8'") + @Getter + private final String teamColor; + + private static final Team pub = new Team(0, "PUB", "Öffentlich", "8"); + + public static Team get(int id) { + if(id == 0) + return pub; + return select.select(id); + } + + public List getMembers(){ + return SteamwarUser.getTeam(teamId).stream().map(SteamwarUser::getId).collect(Collectors.toList()); + } +} diff --git a/src/de/steamwar/sql/TeamTeilnahme.java b/src/de/steamwar/sql/TeamTeilnahme.java new file mode 100644 index 0000000..533b830 --- /dev/null +++ b/src/de/steamwar/sql/TeamTeilnahme.java @@ -0,0 +1,54 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.util.Set; +import java.util.stream.Collectors; + +@AllArgsConstructor +public class TeamTeilnahme { + + private static final Table table = new Table<>(TeamTeilnahme.class); + private static final SelectStatement select = table.select(Table.PRIMARY); + private static final SelectStatement selectTeams = table.selectFields("EventID"); + private static final SelectStatement selectEvents = table.selectFields("TeamID"); + + @Field(keys = {Table.PRIMARY}) + private final int teamId; + @Field(keys = {Table.PRIMARY}) + private final int eventId; + + public static boolean nimmtTeil(int teamID, int eventID){ + return select.select(teamID, eventID) != null; + } + + public static Set getTeams(int eventID){ + return selectTeams.listSelect(eventID).stream().map(tt -> Team.get(tt.teamId)).collect(Collectors.toSet()); // suboptimal performance (O(n) database queries) + } + + public static Set getEvents(int teamID){ + return selectEvents.listSelect(teamID).stream().map(tt -> Event.get(tt.eventId)).collect(Collectors.toSet()); // suboptimal performance (O(n) database queries) + } +} diff --git a/src/de/steamwar/sql/UserConfig.java b/src/de/steamwar/sql/UserConfig.java new file mode 100644 index 0000000..0b45f1f --- /dev/null +++ b/src/de/steamwar/sql/UserConfig.java @@ -0,0 +1,72 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.util.UUID; + +@AllArgsConstructor +public class UserConfig { + + private static final Table table = new Table<>(UserConfig.class); + private static final SelectStatement select = table.select(Table.PRIMARY); + private static final Statement insert = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + @Field(keys = {Table.PRIMARY}) + private final int user; + @Field(keys = {Table.PRIMARY}) + private final String config; + @Field + private final String value; + + public static String getConfig(UUID player, String config) { + return getConfig(SteamwarUser.get(player).getId(), config); + } + + public static String getConfig(int player, String config) { + return select.select(player, config).value; + } + + public static void updatePlayerConfig(UUID uuid, String config, String value) { + updatePlayerConfig(SteamwarUser.get(uuid).getId(), config, value); + } + + public static void updatePlayerConfig(int id, String config, String value) { + if (value == null) { + removePlayerConfig(id, config); + return; + } + insert.update(id, config, value); + } + + public static void removePlayerConfig(UUID uuid, String config) { + removePlayerConfig(SteamwarUser.get(uuid).getId(), config); + } + + public static void removePlayerConfig(int id, String config) { + delete.update(id, config); + } +} diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 2e9edb8..c436fc2 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -39,10 +39,14 @@ public class Statement implements AutoCloseable { private static final Supplier conProvider; static final Consumer> schemaCreator; + private static final boolean mysqlMode; + private static final boolean productionDatabase; + static { File file = new File(System.getProperty("user.home"), "mysql.properties"); + mysqlMode = file.exists(); - if(file.exists()) { + if(mysqlMode) { Properties properties = new Properties(); try { properties.load(new FileReader(file)); @@ -54,6 +58,7 @@ public class Statement implements AutoCloseable { String user = properties.getProperty("user"); String password = properties.getProperty("password"); + productionDatabase = "core".equals(properties.getProperty("database")); MAX_CONNECTIONS = SQLConfig.impl.maxConnections(); conProvider = () -> { try { @@ -64,7 +69,6 @@ public class Statement implements AutoCloseable { }; schemaCreator = table -> {}; } else { - MAX_CONNECTIONS = 1; Connection connection; try { @@ -74,6 +78,8 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create sqlite connection", e); } + productionDatabase = false; + MAX_CONNECTIONS = 1; conProvider = () -> connection; schemaCreator = Table::ensureExistanceInSqlite; } @@ -92,6 +98,14 @@ public class Statement implements AutoCloseable { } } + public static boolean mysqlMode() { + return mysqlMode; + } + + public static boolean productionDatabase() { + return productionDatabase; + } + private final boolean returnGeneratedKeys; private final String sql; private final Map cachedStatements = new HashMap<>(); diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index ffef01c..937f54d 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -59,7 +59,6 @@ public class Table { Statement.schemaCreator.accept(this); } - public SelectStatement select(String name) { return selectFields(keyFields(name)); } @@ -108,7 +107,7 @@ public class Table { List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); try (Statement statement = new Statement( "CREATE TABLE IF NOT EXISTS " + name + "(" + - Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? "" : " NOT NULL DEFAULT NULL") + (!field.field.nullable() && field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + + Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + ") STRICT, WITHOUT ROWID")) { statement.update(); -- 2.39.2 From d6213acab0e87e4d622c5a6b9611e3ba55289b7c Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 29 Oct 2022 12:42:41 +0200 Subject: [PATCH 13/16] Fix merge issues --- src/de/steamwar/sql/SWException.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index af1f9c1..3e8084b 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -29,6 +29,10 @@ import java.io.File; @AllArgsConstructor public class SWException { + public static void init() { + // force class initialialisation + } + private static final String CWD = System.getProperty("user.dir"); private static final String SERVER_NAME = new File(CWD).getName(); -- 2.39.2 From 92bea6255f6cae312674c7badffb7a8a44c4daee Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 29 Oct 2022 13:19:36 +0200 Subject: [PATCH 14/16] Fixes --- build.gradle | 2 +- src/de/steamwar/sql/SWException.java | 7 ++++++- src/de/steamwar/sql/internal/SqlTypeMapper.java | 4 ++-- src/de/steamwar/sql/internal/Statement.java | 6 ++++++ src/de/steamwar/sql/internal/Table.java | 6 +++--- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 48d33bd..d5f000a 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - implementation 'org.xerial:sqlite-jdbc:3.36.0' + compileOnly 'org.xerial:sqlite-jdbc:3.36.0' } task buildResources { diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index 3e8084b..4354867 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -25,6 +25,7 @@ import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import java.io.File; +import java.sql.Timestamp; @AllArgsConstructor public class SWException { @@ -37,8 +38,12 @@ public class SWException { private static final String SERVER_NAME = new File(CWD).getName(); private static final Table table = new Table<>(SWException.class, "Exception"); - private static final Statement insert = table.insertAll(); + private static final Statement insert = table.insertFields("server", "message", "stacktrace"); + @Field(keys = {Table.PRIMARY}) + private final int id; + @Field(def = "CURRENT_TIMESTAMP") + private final Timestamp time; @Field private final String server; @Field diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index d364bc4..5c41010 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -30,7 +30,7 @@ import java.util.Map; public final class SqlTypeMapper { private static final Map, SqlTypeMapper> mappers = new IdentityHashMap<>(); - public static SqlTypeMapper getMapper(Class clazz) { + public static SqlTypeMapper getMapper(Class clazz) { return (SqlTypeMapper) mappers.get(clazz); } @@ -47,7 +47,7 @@ public final class SqlTypeMapper { public static > void nameEnumMapper(Class type) { new SqlTypeMapper<>( type, - "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo) + ")", + "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).get() + ")", (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)), (st, index, value) -> st.setString(index, value.name()) ); diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index c436fc2..152784f 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -115,6 +115,7 @@ public class Statement implements AutoCloseable { } public Statement(String sql, boolean returnGeneratedKeys) { + System.out.println(sql); this.sql = sql; this.returnGeneratedKeys = returnGeneratedKeys; synchronized (statements) { @@ -165,6 +166,11 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not test connection validity", ex); } + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + throw new SecurityException("Failing sql statement", e); } diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index 937f54d..f9dd6f7 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -107,9 +107,9 @@ public class Table { List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); try (Statement statement = new Statement( "CREATE TABLE IF NOT EXISTS " + name + "(" + - Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + + Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + - ") STRICT, WITHOUT ROWID")) { + ") WITHOUT ROWID")) { statement.update(); } } @@ -127,7 +127,7 @@ public class Table { private TableField(java.lang.reflect.Field field) { this.identifier = field.getName(); - this.mapper = (SqlTypeMapper) SqlTypeMapper.getMapper(field.getDeclaringClass()); + this.mapper = SqlTypeMapper.getMapper(field.getType()); this.field = field.getAnnotation(Field.class); } -- 2.39.2 From bd626bb4e6366f1e5e7e64765a6e47fefe05b16a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 2 Nov 2022 22:38:21 +0100 Subject: [PATCH 15/16] Current state --- src/de/steamwar/sql/NodeMember.java | 21 ++++++++++++----- src/de/steamwar/sql/Replay.java | 5 ++-- src/de/steamwar/sql/SWException.java | 2 +- src/de/steamwar/sql/SchematicNode.java | 6 ++++- src/de/steamwar/sql/SteamwarUser.java | 13 +++++++++-- .../sql/internal/SelectStatement.java | 2 +- .../steamwar/sql/internal/SqlTypeMapper.java | 4 ++-- src/de/steamwar/sql/internal/Statement.java | 23 ++++++++++++------- src/de/steamwar/sql/internal/Table.java | 13 +++++------ 9 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index f0de53c..32036ab 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -24,7 +24,6 @@ import de.steamwar.sql.internal.SelectStatement; import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; -import lombok.Getter; import java.util.HashSet; import java.util.Set; @@ -32,6 +31,10 @@ import java.util.Set; @AllArgsConstructor public class NodeMember { + public static void init() { + // enforce class initialization + } + private static final Table table = new Table<>(NodeMember.class); private static final SelectStatement getNodeMember = table.select(Table.PRIMARY); private static final SelectStatement getNodeMembers = table.selectFields("Node"); @@ -39,15 +42,21 @@ public class NodeMember { private static final Statement create = table.insertAll(); private static final Statement delete = table.delete(Table.PRIMARY); - @Getter @Field(keys = {Table.PRIMARY}) - private final int node; - @Getter + private final int nodeId; @Field(keys = {Table.PRIMARY}) - private final int member; + private final int userId; + + public int getNodeId() { + return nodeId; + } + + public int getUserId() { + return userId; + } public void delete() { - delete.update(node, member); + delete.update(nodeId, userId); } public static NodeMember createNodeMember(int node, int member) { diff --git a/src/de/steamwar/sql/Replay.java b/src/de/steamwar/sql/Replay.java index 90d2b34..a90e239 100644 --- a/src/de/steamwar/sql/Replay.java +++ b/src/de/steamwar/sql/Replay.java @@ -28,6 +28,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.sql.SQLException; @AllArgsConstructor @@ -36,9 +37,9 @@ public class Replay { static { new SqlTypeMapper<>(File.class, "BLOB", (rs, identifier) -> { try { - File file = File.createTempFile("replay", "replay"); + File file = File.createTempFile("replay", ".replay"); file.deleteOnExit(); - Files.copy(rs.getBinaryStream(identifier), file.toPath()); + Files.copy(rs.getBinaryStream(identifier), file.toPath(), StandardCopyOption.REPLACE_EXISTING); return file; } catch (IOException e) { throw new SQLException(e); diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index 4354867..0a8ece3 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -40,7 +40,7 @@ public class SWException { private static final Table table = new Table<>(SWException.class, "Exception"); private static final Statement insert = table.insertFields("server", "message", "stacktrace"); - @Field(keys = {Table.PRIMARY}) + @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int id; @Field(def = "CURRENT_TIMESTAMP") private final Timestamp time; diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index cc62acc..be7f1b1 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -72,6 +72,10 @@ public class SchematicNode { private static final SelectStatement allAccessibleByUser = new SelectStatement<>(table, "WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN ORDER BY NodeName"); private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (" + nodeSelectCreator("") + "WHERE NodeId = ? UNION " + nodeSelectCreator("SN.") + "SN, RSN WHERE RSN.ParentNode = SN.NodeId) SELECT * FROM RSN ORDER BY NodeName"); + static { + NodeMember.init(); + } + @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int nodeId; @Field(keys = {"OwnerNameParent"}) @@ -476,7 +480,7 @@ public class SchematicNode { final Set nodeMembers = NodeMember.getSchematics(user.getId()); AtomicInteger i = new AtomicInteger(); i.set(currentNode.getId()); - while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) { + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNodeId() == i.get())) { currentNode = currentNode.getParentNode(); i.set(currentNode.getId()); builder.insert(0, split) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index c5ccbd5..874643f 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -101,7 +101,7 @@ public class SteamwarUser { this.leader = leader; this.locale = locale; this.manualLocale = manualLocale; - this.discordId = discordId != 0 ? discordId : null; + this.discordId = discordId != null && discordId != 0 ? discordId : null; usersById.put(id, this); usersByName.put(userName.toLowerCase(), this); @@ -118,7 +118,16 @@ public class SteamwarUser { public Locale getLocale() { if(locale != null) return locale; - return Locale.getDefault(); //TODO test correct locale on join (z.B. FightSystem Hotbarkit) + return Locale.getDefault(); + } + + public void setLocale(Locale locale, boolean manualLocale) { + if (locale == null || (this.manualLocale && !manualLocale)) + return; + + this.locale = locale; + this.manualLocale = manualLocale; + updateLocale.update(locale.toLanguageTag(), manualLocale, id); } public static SteamwarUser get(String userName){ diff --git a/src/de/steamwar/sql/internal/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java index 0f4a329..e17dcb7 100644 --- a/src/de/steamwar/sql/internal/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -31,7 +31,7 @@ public class SelectStatement extends Statement { private final Table table; SelectStatement(Table table, String... kfields) { - this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); } public SelectStatement(Table table, String sql) { diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index 5c41010..ae3b7c8 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -47,7 +47,7 @@ public final class SqlTypeMapper { public static > void nameEnumMapper(Class type) { new SqlTypeMapper<>( type, - "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).get() + ")", + "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).orElse(0) + ")", (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)), (st, index, value) -> st.setString(index, value.name()) ); @@ -57,7 +57,7 @@ public final class SqlTypeMapper { primitiveMapper(boolean.class, Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); primitiveMapper(byte.class, Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); primitiveMapper(short.class, Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); - primitiveMapper(int.class, Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); + primitiveMapper(int.class, Integer.class, "INTEGER", ResultSet::getInt, PreparedStatement::setInt); primitiveMapper(long.class, Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); primitiveMapper(float.class, Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); primitiveMapper(double.class, Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 152784f..88aa5d6 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -26,6 +26,7 @@ import java.sql.*; import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,15 +39,17 @@ public class Statement implements AutoCloseable { private static final int MAX_CONNECTIONS; private static final Supplier conProvider; static final Consumer> schemaCreator; + static final String ON_DUPLICATE_KEY; + static final UnaryOperator upsertWrapper; - private static final boolean mysqlMode; - private static final boolean productionDatabase; + private static final boolean MYSQL_MODE; + private static final boolean PRODUCTION_DATABASE; static { File file = new File(System.getProperty("user.home"), "mysql.properties"); - mysqlMode = file.exists(); + MYSQL_MODE = file.exists(); - if(mysqlMode) { + if(MYSQL_MODE) { Properties properties = new Properties(); try { properties.load(new FileReader(file)); @@ -58,7 +61,7 @@ public class Statement implements AutoCloseable { String user = properties.getProperty("user"); String password = properties.getProperty("password"); - productionDatabase = "core".equals(properties.getProperty("database")); + PRODUCTION_DATABASE = "core".equals(properties.getProperty("database")); MAX_CONNECTIONS = SQLConfig.impl.maxConnections(); conProvider = () -> { try { @@ -68,6 +71,8 @@ public class Statement implements AutoCloseable { } }; schemaCreator = table -> {}; + ON_DUPLICATE_KEY = " ON DUPLICATE KEY UPDATE "; + upsertWrapper = f -> f + " = VALUES(" + f + ")"; } else { Connection connection; @@ -78,10 +83,12 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create sqlite connection", e); } - productionDatabase = false; + PRODUCTION_DATABASE = false; MAX_CONNECTIONS = 1; conProvider = () -> connection; schemaCreator = Table::ensureExistanceInSqlite; + ON_DUPLICATE_KEY = " ON CONFLICT DO UPDATE SET "; + upsertWrapper = f -> f + " = " + f; } } @@ -99,11 +106,11 @@ public class Statement implements AutoCloseable { } public static boolean mysqlMode() { - return mysqlMode; + return MYSQL_MODE; } public static boolean productionDatabase() { - return productionDatabase; + return PRODUCTION_DATABASE; } private final boolean returnGeneratedKeys; diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index f9dd6f7..fb72709 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -53,7 +53,7 @@ public class Table { keys = Arrays.stream(fields).flatMap(field -> Arrays.stream(field.field.keys())).distinct().collect(Collectors.toMap(Function.identity(), key -> Arrays.stream(fields).filter(field -> Arrays.asList(field.field.keys()).contains(key)).toArray(TableField[]::new))); for (TableField field : fields) { - fieldsByIdentifier.put(field.identifier, field); + fieldsByIdentifier.put(field.identifier.toLowerCase(), field); } Statement.schemaCreator.accept(this); @@ -91,8 +91,8 @@ public class Table { } public Statement insertFields(boolean returnGeneratedKeys, String... fields) { - List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f).field.keys().length == 0).collect(Collectors.toList()); - return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", "))), returnGeneratedKeys); + List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f.toLowerCase()).field.keys().length == 0).collect(Collectors.toList()); + return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : Statement.ON_DUPLICATE_KEY + nonKeyFields.stream().map(Statement.upsertWrapper).collect(Collectors.joining(", "))), returnGeneratedKeys); } public Statement delete(String name) { @@ -104,12 +104,11 @@ public class Table { } void ensureExistanceInSqlite() { - List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); try (Statement statement = new Statement( "CREATE TABLE IF NOT EXISTS " + name + "(" + - Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + - keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + - ") WITHOUT ROWID")) { + Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def())).collect(Collectors.joining(", ")) + + keys.entrySet().stream().map(key -> (key.getKey().equals(PRIMARY) ? ", PRIMARY KEY(" : ", UNIQUE (") + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + + ")")) { statement.update(); } } -- 2.39.2 From e9a39d007d83a93e7dc50ab2706c9f16213bc900 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 15 Nov 2022 18:40:48 +0100 Subject: [PATCH 16/16] Current state --- src/de/steamwar/sql/NodeMember.java | 8 ++-- src/de/steamwar/sql/SchematicNode.java | 6 +-- src/de/steamwar/sql/UserConfig.java | 3 +- .../steamwar/sql/internal/SqlTypeMapper.java | 2 + src/de/steamwar/sql/internal/Statement.java | 45 ++++++++++--------- src/de/steamwar/sql/internal/Table.java | 4 +- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index 32036ab..e704ad0 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -37,8 +37,8 @@ public class NodeMember { private static final Table table = new Table<>(NodeMember.class); private static final SelectStatement getNodeMember = table.select(Table.PRIMARY); - private static final SelectStatement getNodeMembers = table.selectFields("Node"); - private static final SelectStatement getSchematics = table.selectFields("Member"); + private static final SelectStatement getNodeMembers = table.selectFields("NodeId"); + private static final SelectStatement getSchematics = table.selectFields("UserId"); private static final Statement create = table.insertAll(); private static final Statement delete = table.delete(Table.PRIMARY); @@ -47,11 +47,11 @@ public class NodeMember { @Field(keys = {Table.PRIMARY}) private final int userId; - public int getNodeId() { + public int getNode() { return nodeId; } - public int getUserId() { + public int getMember() { return userId; } diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index be7f1b1..78fa24b 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -139,7 +139,7 @@ public class SchematicNode { public static SchematicNode createSchematicNode(int owner, String name, Integer parent, String type, String item) { if (parent != null && parent == 0) parent = null; - int nodeId = create.insertGetKey(owner, name, parent, type, item); + int nodeId = create.insertGetKey(owner, name, parent, item, type); return getSchematicNode(nodeId); } @@ -447,7 +447,7 @@ public class SchematicNode { private void updateDB() { this.lastUpdate = Timestamp.from(Instant.now()); - update.update(nodeId, nodeOwner, nodeName, parentNode, nodeItem, nodeType, lastUpdate, nodeRank, replaceColor, allowReplay, nodeFormat); + update.update(nodeId, nodeOwner, nodeName, parentNode, lastUpdate, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat); this.brCache.clear(); TAB_CACHE.clear(); } @@ -480,7 +480,7 @@ public class SchematicNode { final Set nodeMembers = NodeMember.getSchematics(user.getId()); AtomicInteger i = new AtomicInteger(); i.set(currentNode.getId()); - while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNodeId() == i.get())) { + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) { currentNode = currentNode.getParentNode(); i.set(currentNode.getId()); builder.insert(0, split) diff --git a/src/de/steamwar/sql/UserConfig.java b/src/de/steamwar/sql/UserConfig.java index 0b45f1f..f705dfd 100644 --- a/src/de/steamwar/sql/UserConfig.java +++ b/src/de/steamwar/sql/UserConfig.java @@ -47,7 +47,8 @@ public class UserConfig { } public static String getConfig(int player, String config) { - return select.select(player, config).value; + UserConfig value = select.select(player, config); + return value != null ? value.value : null; } public static void updatePlayerConfig(UUID uuid, String config, String value) { diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index ae3b7c8..34c6173 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -19,6 +19,7 @@ package de.steamwar.sql.internal; +import java.io.InputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -63,6 +64,7 @@ public final class SqlTypeMapper { primitiveMapper(double.class, Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString); new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp); + new SqlTypeMapper<>(InputStream.class, "BLOB", ResultSet::getBinaryStream, PreparedStatement::setBinaryStream); } private static void primitiveMapper(Class primitive, Class wrapped, String sqlType, SQLReader reader, SQLWriter writer) { diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 88aa5d6..6c44332 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -122,7 +122,6 @@ public class Statement implements AutoCloseable { } public Statement(String sql, boolean returnGeneratedKeys) { - System.out.println(sql); this.sql = sql; this.returnGeneratedKeys = returnGeneratedKeys; synchronized (statements) { @@ -159,34 +158,35 @@ public class Statement implements AutoCloseable { private T withConnection(SQLRunnable runnable, Object... objects) { Connection connection = aquireConnection(); - T result; try { - result = tryWithConnection(connection, runnable, objects); - } catch (SQLException e) { try { - if(connection.isClosed() || !connection.isValid(1)) { + return tryWithConnection(connection, runnable, objects); + } finally { + if(connectionInvalid(connection)) { closeConnection(connection); - return withConnection(runnable, objects); + } else { + synchronized (connections) { + connections.push(connection); + connections.notify(); + } } - } catch (SQLException ex) { - closeConnection(connection); - throw new SecurityException("Could not test connection validity", ex); } - - synchronized (connections) { - connections.push(connection); - connections.notify(); + } catch (SQLException e) { + if(connectionInvalid(connection)) { + return withConnection(runnable, objects); + } else { + throw new SecurityException("Failing sql statement", e); } - - throw new SecurityException("Failing sql statement", e); } + } - synchronized (connections) { - connections.push(connection); - connections.notify(); + private boolean connectionInvalid(Connection connection) { + try { + return connection.isClosed(); + } catch (SQLException e) { + logger.log(Level.INFO, "Could not check SQL connection status", e); // No database logging possible at this state + return true; } - - return result; } private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { @@ -201,7 +201,10 @@ public class Statement implements AutoCloseable { for (int i = 0; i < objects.length; i++) { Object o = objects[i]; - SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o); + if(o != null) + SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o); + else + st.setNull(i+1, Types.NULL); } return runnable.run(st); diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index fb72709..cbad10a 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -75,7 +75,7 @@ public class Table { } public Statement updateFields(String[] fields, String... kfields) { - return new Statement("UPDATE " + name + " SET " + Arrays.stream(fields).map(f -> f + " = ?").collect(Collectors.joining(", ")) + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + return new Statement("UPDATE " + name + " SET " + Arrays.stream(fields).map(f -> f + " = ?").collect(Collectors.joining(", ")) + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); } public Statement insert(String name) { @@ -100,7 +100,7 @@ public class Table { } public Statement deleteFields(String... kfields) { - return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); } void ensureExistanceInSqlite() { -- 2.39.2