001/* 002 * PermissionsEx 003 * Copyright (C) zml and PermissionsEx contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ca.stellardrift.permissionsex.sql.hikari; 018 019import com.zaxxer.hikari.HikariConfig; 020import com.zaxxer.hikari.HikariDataSource; 021import org.checkerframework.checker.nullness.qual.Nullable; 022import org.h2.engine.ConnectionInfo; 023import org.pcollections.HashTreePMap; 024import org.pcollections.PMap; 025 026import java.nio.file.Path; 027import java.nio.file.Paths; 028import java.sql.DriverManager; 029import java.sql.SQLException; 030import java.util.Properties; 031import java.util.function.BiFunction; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034 035import static java.util.Objects.requireNonNull; 036 037public class Hikari { 038 private static final Pattern JDBC_URL_REGEX = Pattern.compile("(?:jdbc:)?([^:]+):(//)?(?:([^:]+)(?::([^@]+))?@)?(.*)"); 039 040 /** 041 * Map from protocol names to a function that transforms the given JDBC url 042 */ 043 private static final PMap<String, BiFunction<Path, String, String>> PATH_CANONICALIZERS = HashTreePMap.singleton("h2", (baseDir, orig) ->{ 044 // Bleh if only h2 had a better way of supplying a base directory... oh well... 045 final ConnectionInfo h2Info = new ConnectionInfo(orig); 046 if (!h2Info.isPersistent() || h2Info.isRemote()) { 047 return orig; 048 } 049 050 String url = orig; 051 if (url.startsWith("file:")) { 052 url = orig.substring("file:".length()); 053 } 054 055 final Path origPath = Paths.get(url); 056 if (origPath.isAbsolute()) { 057 return origPath.toString(); 058 } else { 059 return baseDir.toAbsolutePath().resolve(origPath).toString().replace('\\', '/'); 060 } 061 }); 062 063 /** 064 * Properties specific to a certain JDBC protocol, immutable. 065 * 066 * Protocols are identified by their jdbc driver names. 067 */ 068 private static final PMap<String, Properties> PROTOCOL_SPECIFIC_PROPS; 069 070 static { 071 final Properties mysqlProps = new Properties(); 072 // Config options based on http://assets.en.oreilly.com/1/event/21/Connector_J%20Performance%20Gems%20Presentation.pdf 073 mysqlProps.setProperty("useConfigs", "maxPerformance"); 074 075 PROTOCOL_SPECIFIC_PROPS = HashTreePMap.<String, Properties>empty() 076 .plus("com.mysql.jdbc.Driver", mysqlProps) 077 .plus("org.maridadb.jdbc.Driver", mysqlProps); 078 } 079 080 /** 081 * Create a data source for the provided URL, relative to the current working directory. 082 * 083 * @param jdbcUrl URL to connect to 084 * @return a new data source 085 * @throws SQLException if unable to resolve a driver for the URL 086 * @since 2.0.0 087 */ 088 public static HikariDataSource createDataSource(final String jdbcUrl) throws SQLException { 089 return createDataSource(jdbcUrl, Paths.get(".")); 090 } 091 092 /** 093 * Create a data source for the provided {@code jdbcUrl}, with any filesystem 094 * paths made relative to {@code baseDir}. 095 * 096 * @param jdbcUrl URL to connect to 097 * @param baseDir base directory 098 * @return a new data source 099 * @throws SQLException if unable to resolve a driver for the URL 100 * @since 2.0.0 101 */ 102 public static HikariDataSource createDataSource(final String jdbcUrl, final Path baseDir) throws SQLException { 103 // Based on Sponge`s code, but without alias handling and caching 104 final Matcher match = JDBC_URL_REGEX.matcher(requireNonNull(jdbcUrl, "jdbcUrl")); 105 if (!match.matches()) { 106 throw new IllegalArgumentException("URL " + jdbcUrl + " is not a valid JDBC URL"); 107 } 108 109 final String protocol = match.group(1); 110 final boolean hasSlashes = match.group(2) != null; 111 final @Nullable String user = match.group(3); 112 final @Nullable String pass = match.group(4); 113 String serverDatabaseSpecifier = match.group(5); 114 final BiFunction<Path, String, String> derelativizer = PATH_CANONICALIZERS.get(protocol); 115 if (derelativizer != null) { 116 serverDatabaseSpecifier = derelativizer.apply(baseDir, serverDatabaseSpecifier); 117 } 118 119 final String unauthedUrl = new StringBuilder("jdbc:") 120 .append(protocol) 121 .append(hasSlashes ? "://" : ":") 122 .append(serverDatabaseSpecifier) 123 .toString(); 124 final String driverClass = DriverManager.getDriver(unauthedUrl).getClass().getCanonicalName(); 125 126 final HikariConfig config = new HikariConfig(); 127 config.setUsername(user); 128 config.setPassword(pass); 129 config.setDriverClassName(driverClass); 130 131 // https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing for info on pool sizing 132 config.setMaximumPoolSize((Runtime.getRuntime().availableProcessors() * 2) + 1); 133 final @Nullable Properties driverSpecificProperties = PROTOCOL_SPECIFIC_PROPS.get(driverClass); 134 final Properties dsProps; 135 if (driverSpecificProperties == null) { 136 dsProps = new Properties(); 137 } else { 138 dsProps = new Properties(driverSpecificProperties); 139 } 140 dsProps.setProperty("baseDir", baseDir.toAbsolutePath().toString()); 141 config.setDataSourceProperties(dsProps); 142 config.setJdbcUrl(unauthedUrl); 143 return new HikariDataSource(config); 144 } 145 146 private Hikari() {} 147}