PageRenderTime 17ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/worldguard-legacy/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLDriver.java

https://gitlab.com/igserfurtmcschulserver/CustomWorldGuard
Java | 257 lines | 156 code | 28 blank | 73 comment | 8 complexity | 04d87aa877991b4ceee1451eaf3b0c7a MD5 | raw file
  1. /*
  2. * WorldGuard, a suite of tools for Minecraft
  3. * Copyright (C) sk89q <http://www.sk89q.com>
  4. * Copyright (C) WorldGuard team and contributors
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU Lesser General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but WITHOUT
  12. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  14. * for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.sk89q.worldguard.protection.managers.storage.sql;
  20. import com.sk89q.worldguard.protection.managers.storage.RegionDatabase;
  21. import com.sk89q.worldguard.protection.managers.storage.RegionDriver;
  22. import com.sk89q.worldguard.protection.managers.storage.StorageException;
  23. import com.sk89q.worldguard.util.io.Closer;
  24. import com.sk89q.worldguard.util.sql.DataSourceConfig;
  25. import org.flywaydb.core.Flyway;
  26. import org.flywaydb.core.api.FlywayException;
  27. import org.flywaydb.core.api.MigrationVersion;
  28. import java.sql.Connection;
  29. import java.sql.ResultSet;
  30. import java.sql.SQLException;
  31. import java.sql.Statement;
  32. import java.util.ArrayList;
  33. import java.util.HashMap;
  34. import java.util.List;
  35. import java.util.Map;
  36. import java.util.concurrent.Callable;
  37. import java.util.concurrent.ExecutionException;
  38. import java.util.concurrent.ExecutorService;
  39. import java.util.concurrent.Executors;
  40. import java.util.concurrent.Future;
  41. import java.util.concurrent.TimeUnit;
  42. import java.util.concurrent.TimeoutException;
  43. import java.util.logging.Level;
  44. import java.util.logging.Logger;
  45. import static com.google.common.base.Preconditions.checkNotNull;
  46. /**
  47. * Stores regions using a JDBC connection with support for SQL.
  48. *
  49. * <p>Note, however, that this implementation <strong>only supports MySQL.
  50. * </strong></p>
  51. */
  52. public class SQLDriver implements RegionDriver {
  53. private static final Logger log = Logger.getLogger(SQLDriver.class.getCanonicalName());
  54. private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
  55. private static final int CONNECTION_TIMEOUT = 6000;
  56. private final DataSourceConfig config;
  57. private boolean initialized = false;
  58. /**
  59. * Create a new instance.
  60. *
  61. * @param config a configuration
  62. */
  63. public SQLDriver(DataSourceConfig config) {
  64. checkNotNull(config);
  65. this.config = config;
  66. }
  67. @Override
  68. public RegionDatabase get(String name) {
  69. return new SQLRegionDatabase(this, name);
  70. }
  71. @Override
  72. public List<RegionDatabase> getAll() throws StorageException {
  73. Closer closer = Closer.create();
  74. try {
  75. List<RegionDatabase> stores = new ArrayList<RegionDatabase>();
  76. Connection connection = closer.register(getConnection());
  77. Statement stmt = connection.createStatement();
  78. ResultSet rs = closer.register(stmt.executeQuery("SELECT name FROM " + config.getTablePrefix() + "world"));
  79. while (rs.next()) {
  80. stores.add(get(rs.getString(1)));
  81. }
  82. return stores;
  83. } catch (SQLException e) {
  84. throw new StorageException("Failed to fetch list of worlds", e);
  85. } finally {
  86. closer.closeQuietly();
  87. }
  88. }
  89. /**
  90. * Perform initialization if it hasn't been (successfully) performed yet.
  91. *
  92. * @throws StorageException thrown on error
  93. */
  94. synchronized void initialize() throws StorageException {
  95. if (!initialized) {
  96. try {
  97. migrate();
  98. } catch (SQLException e) {
  99. throw new StorageException("Failed to migrate database tables", e);
  100. }
  101. initialized = true;
  102. }
  103. }
  104. /**
  105. * Attempt to migrate the tables to the latest version.
  106. *
  107. * @throws StorageException thrown if migration fails
  108. * @throws SQLException thrown on SQL error
  109. */
  110. private void migrate() throws SQLException, StorageException {
  111. Closer closer = Closer.create();
  112. Connection conn = closer.register(getConnection());
  113. try {
  114. // Check some tables
  115. boolean tablesExist;
  116. boolean isRecent;
  117. boolean isBeforeMigrations;
  118. boolean hasMigrations;
  119. try {
  120. tablesExist = tryQuery(conn, "SELECT * FROM " + config.getTablePrefix() + "region_cuboid LIMIT 1");
  121. isRecent = tryQuery(conn, "SELECT world_id FROM " + config.getTablePrefix() + "region_cuboid LIMIT 1");
  122. isBeforeMigrations = !tryQuery(conn, "SELECT uuid FROM " + config.getTablePrefix() + "user LIMIT 1");
  123. hasMigrations = tryQuery(conn, "SELECT * FROM " + config.getTablePrefix() + "migrations LIMIT 1");
  124. } finally {
  125. closer.closeQuietly();
  126. }
  127. // We don't bother with migrating really old tables
  128. if (tablesExist && !isRecent) {
  129. throw new StorageException(
  130. "Sorry, your tables are too old for the region SQL auto-migration system. " +
  131. "Please run region_manual_update_20110325.sql on your database, which comes " +
  132. "with WorldGuard or can be found in http://github.com/sk89q/worldguard");
  133. }
  134. // Our placeholders
  135. Map<String, String> placeHolders = new HashMap<String, String>();
  136. placeHolders.put("tablePrefix", config.getTablePrefix());
  137. Flyway flyway = new Flyway();
  138. // The SQL support predates the usage of Flyway, so let's do some
  139. // checks and issue messages appropriately
  140. if (!hasMigrations) {
  141. flyway.setInitOnMigrate(true);
  142. if (tablesExist) {
  143. // Detect if this is before migrations
  144. if (isBeforeMigrations) {
  145. flyway.setInitVersion(MigrationVersion.fromVersion("1"));
  146. }
  147. log.log(Level.INFO, "The SQL region tables exist but the migrations table seems to not exist yet. Creating the migrations table...");
  148. } else {
  149. // By default, if Flyway sees any tables at all in the schema, it
  150. // will assume that we are up to date, so we have to manually
  151. // check ourselves and then ask Flyway to start from the beginning
  152. // if our test table doesn't exist
  153. flyway.setInitVersion(MigrationVersion.fromVersion("0"));
  154. log.log(Level.INFO, "SQL region tables do not exist: creating...");
  155. }
  156. }
  157. flyway.setClassLoader(getClass().getClassLoader());
  158. flyway.setLocations("migrations/region/" + getMigrationFolderName());
  159. flyway.setDataSource(config.getDsn(), config.getUsername(), config.getPassword());
  160. flyway.setTable(config.getTablePrefix() + "migrations");
  161. flyway.setPlaceholders(placeHolders);
  162. flyway.setValidateOnMigrate(false);
  163. flyway.migrate();
  164. } catch (FlywayException e) {
  165. throw new StorageException("Failed to migrate tables", e);
  166. } finally {
  167. closer.closeQuietly();
  168. }
  169. }
  170. /**
  171. * Get the name of the folder in migrations/region containing the migration files.
  172. *
  173. * @return the migration folder name
  174. */
  175. public String getMigrationFolderName() {
  176. return "mysql";
  177. }
  178. /**
  179. * Try to execute a query and return true if it did not fail.
  180. *
  181. * @param conn the connection to run the query on
  182. * @param sql the SQL query
  183. * @return true if the query did not end in error
  184. */
  185. private boolean tryQuery(Connection conn, String sql) {
  186. Closer closer = Closer.create();
  187. try {
  188. Statement statement = closer.register(conn.createStatement());
  189. statement.executeQuery(sql);
  190. return true;
  191. } catch (SQLException ex) {
  192. return false;
  193. } finally {
  194. closer.closeQuietly();
  195. }
  196. }
  197. /**
  198. * Get the database configuration.
  199. *
  200. * @return the database configuration
  201. */
  202. DataSourceConfig getConfig() {
  203. return config;
  204. }
  205. /**
  206. * Create a new connection.
  207. *
  208. * @return the connection
  209. * @throws SQLException raised if the connection cannot be instantiated
  210. */
  211. Connection getConnection() throws SQLException {
  212. Future<Connection> future = EXECUTOR.submit(new Callable<Connection>() {
  213. @Override
  214. public Connection call() throws Exception {
  215. return config.getConnection();
  216. }
  217. });
  218. try {
  219. return future.get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
  220. } catch (InterruptedException e) {
  221. throw new SQLException("Failed to get a SQL connection because the operation was interrupted", e);
  222. } catch (ExecutionException e) {
  223. throw new SQLException("Failed to get a SQL connection due to an error", e);
  224. } catch (TimeoutException e) {
  225. future.cancel(true);
  226. throw new SQLException("Failed to get a SQL connection within the time limit");
  227. }
  228. }
  229. }