java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNamed.java
author František Kučera <franta-hg@frantovo.cz>
Tue, 26 Feb 2019 18:19:49 +0100
branchv_0
changeset 236 a3ec71fa8e17
parent 155 eb3676c6929b
permissions -rw-r--r--
Avoid reusing/rewriting the DB connection properties.
There was weird random errors while testing connection to multiple DB in parallel when one of them was meta connection to same DB connection.
Two kinds of exception: 1) missing password 2) „Passing DB password as CLI parameter is insecure!“
franta-hg@16
     1
/**
franta-hg@16
     2
 * SQL-DK
franta-hg@16
     3
 * Copyright © 2013 František Kučera (frantovo.cz)
franta-hg@16
     4
 *
franta-hg@16
     5
 * This program is free software: you can redistribute it and/or modify
franta-hg@16
     6
 * it under the terms of the GNU General Public License as published by
franta-hg@16
     7
 * the Free Software Foundation, either version 3 of the License, or
franta-hg@16
     8
 * (at your option) any later version.
franta-hg@16
     9
 *
franta-hg@16
    10
 * This program is distributed in the hope that it will be useful,
franta-hg@16
    11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
franta-hg@16
    12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
franta-hg@16
    13
 * GNU General Public License for more details.
franta-hg@16
    14
 *
franta-hg@16
    15
 * You should have received a copy of the GNU General Public License
franta-hg@16
    16
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
franta-hg@16
    17
 */
franta-hg@1
    18
package info.globalcode.sql.dk;
franta-hg@1
    19
franta-hg@49
    20
import static info.globalcode.sql.dk.Functions.findByName;
franta-hg@49
    21
import java.sql.Connection;
franta-hg@1
    22
import java.sql.PreparedStatement;
franta-hg@34
    23
import java.sql.SQLException;
franta-hg@49
    24
import java.util.ArrayList;
franta-hg@34
    25
import java.util.List;
franta-hg@51
    26
import java.util.logging.Level;
franta-hg@51
    27
import java.util.logging.Logger;
franta-hg@49
    28
import java.util.regex.Matcher;
franta-hg@49
    29
import java.util.regex.Pattern;
franta-hg@61
    30
import java.util.regex.PatternSyntaxException;
franta-hg@1
    31
franta-hg@1
    32
/**
franta-hg@155
    33
 * Has named parameters.
franta-hg@1
    34
 *
franta-hg@1
    35
 * @author Ing. František Kučera (frantovo.cz)
franta-hg@1
    36
 */
franta-hg@1
    37
public class SQLCommandNamed extends SQLCommand {
franta-hg@1
    38
franta-hg@51
    39
	private static final Logger log = Logger.getLogger(SQLCommandNamed.class.getName());
franta-hg@49
    40
	private String namePrefix;
franta-hg@49
    41
	private String nameSuffix;
franta-hg@34
    42
	private List<NamedParameter> parameters;
franta-hg@49
    43
	private List<NamedParameter> parametersUsed = new ArrayList<>();
franta-hg@49
    44
	private StringBuilder updatedQuery;
franta-hg@49
    45
	private Pattern pattern;
franta-hg@143
    46
	private SQLCommandNumbered numbered;
franta-hg@34
    47
franta-hg@49
    48
	public SQLCommandNamed(String query, List<NamedParameter> parameters, String namePrefix, String nameSuffix) {
franta-hg@37
    49
		super(query);
franta-hg@49
    50
		this.updatedQuery = new StringBuilder(query.length());
franta-hg@34
    51
		this.parameters = parameters;
franta-hg@49
    52
		this.namePrefix = namePrefix;
franta-hg@49
    53
		this.nameSuffix = nameSuffix;
franta-hg@49
    54
	}
franta-hg@49
    55
franta-hg@49
    56
	@Override
franta-hg@49
    57
	public PreparedStatement prepareStatement(Connection c) throws SQLException {
franta-hg@143
    58
		return getSQLCommandNumbered().prepareStatement(c);
franta-hg@143
    59
	}
franta-hg@143
    60
franta-hg@143
    61
	@Override
franta-hg@143
    62
	public void parametrize(PreparedStatement ps) throws SQLException {
franta-hg@143
    63
		getSQLCommandNumbered().parametrize(ps);
franta-hg@143
    64
	}
franta-hg@143
    65
franta-hg@143
    66
	private void prepare() throws SQLException {
franta-hg@61
    67
		try {
franta-hg@61
    68
			buildPattern();
franta-hg@61
    69
			placeParametersAndUpdateQuery();
franta-hg@61
    70
			logPossiblyMissingParameters();
franta-hg@61
    71
		} catch (PatternSyntaxException e) {
franta-hg@61
    72
			throw new SQLException("Name prefix „" + namePrefix + "“ or suffix „" + nameSuffix + "“ contain a wrong regular expression. " + e.getLocalizedMessage(), e);
franta-hg@61
    73
		}
franta-hg@34
    74
	}
franta-hg@34
    75
franta-hg@143
    76
	/**
franta-hg@143
    77
	 * @return SQL command with named parameters converted to SQL command with numbered parameters
franta-hg@143
    78
	 */
franta-hg@143
    79
	public SQLCommandNumbered getSQLCommandNumbered() throws SQLException {
franta-hg@143
    80
		if (numbered == null) {
franta-hg@143
    81
			prepare();
franta-hg@143
    82
			numbered = new SQLCommandNumbered(updatedQuery.toString(), parametersUsed);
franta-hg@49
    83
		}
franta-hg@143
    84
franta-hg@143
    85
		return numbered;
franta-hg@49
    86
	}
franta-hg@49
    87
franta-hg@49
    88
	/**
franta-hg@49
    89
	 * Builds a regexp pattern that matches all parameter names (with prefix/suffix) and which has
franta-hg@49
    90
	 * one group: parameter name (without prefix/suffix)
franta-hg@49
    91
	 */
franta-hg@143
    92
	private void buildPattern() throws PatternSyntaxException {
franta-hg@49
    93
		StringBuilder patternString = new StringBuilder();
franta-hg@49
    94
franta-hg@54
    95
		patternString.append(namePrefix);
franta-hg@54
    96
		patternString.append("(?<paramName>");
franta-hg@49
    97
		for (int i = 0; i < parameters.size(); i++) {
franta-hg@51
    98
			patternString.append(Pattern.quote(parameters.get(i).getName()));
franta-hg@51
    99
			if (i < parameters.size() - 1) {
franta-hg@49
   100
				patternString.append("|");
franta-hg@49
   101
			}
franta-hg@49
   102
		}
franta-hg@49
   103
		patternString.append(")");
franta-hg@54
   104
		patternString.append(nameSuffix);
franta-hg@49
   105
franta-hg@49
   106
		pattern = Pattern.compile(patternString.toString());
franta-hg@49
   107
	}
franta-hg@49
   108
franta-hg@143
   109
	private void placeParametersAndUpdateQuery() {
franta-hg@49
   110
		final String originalQuery = getQuery();
franta-hg@49
   111
		Matcher m = pattern.matcher(originalQuery);
franta-hg@49
   112
franta-hg@49
   113
		int lastPosition = 0;
franta-hg@49
   114
		while (m.find(lastPosition)) {
franta-hg@54
   115
			String name = m.group("paramName");
franta-hg@49
   116
franta-hg@49
   117
			updatedQuery.append(originalQuery.substring(lastPosition, m.start()));
franta-hg@49
   118
			updatedQuery.append("?");
franta-hg@49
   119
franta-hg@49
   120
			parametersUsed.add(findByName(parameters, name));
franta-hg@49
   121
franta-hg@49
   122
			lastPosition = m.end();
franta-hg@49
   123
		}
franta-hg@49
   124
		updatedQuery.append(originalQuery.substring(lastPosition, originalQuery.length()));
franta-hg@49
   125
franta-hg@49
   126
		for (NamedParameter definedParameter : parameters) {
franta-hg@49
   127
			if (findByName(parametersUsed, definedParameter.getName()) == null) {
franta-hg@54
   128
				/**
franta-hg@54
   129
				 * User can have predefined set of parameters and use them with different SQL
franta-hg@54
   130
				 * queries that use only subset of these parameters → just warning, not exception.
franta-hg@54
   131
				 */
franta-hg@54
   132
				log.log(Level.WARNING, "Parameter „{0}“ is defined but not used in the query: „{1}“", new Object[]{definedParameter.getName(), originalQuery});
franta-hg@49
   133
			}
franta-hg@49
   134
		}
franta-hg@1
   135
	}
franta-hg@34
   136
franta-hg@51
   137
	private void logPossiblyMissingParameters() {
franta-hg@78
   138
		Pattern p = Pattern.compile(namePrefix + "(?<paramName>.+?)" + nameSuffix);
franta-hg@51
   139
		Matcher m = p.matcher(updatedQuery);
franta-hg@51
   140
		int lastPosition = 0;
franta-hg@51
   141
		while (m.find(lastPosition)) {
franta-hg@54
   142
			/**
franta-hg@54
   143
			 * We have not parsed and understood the SQL query; the parameter-like looking string
franta-hg@54
   144
			 * could be inside a literal part of the query → just warning, not exception.
franta-hg@54
   145
			 */
franta-hg@54
   146
			log.log(Level.WARNING, "Possibly missing parameter „{0}“ in the query: „{1}“", new Object[]{m.group("paramName"), getQuery()});
franta-hg@51
   147
			lastPosition = m.end();
franta-hg@51
   148
		}
franta-hg@51
   149
	}
franta-hg@51
   150
franta-hg@34
   151
	@Override
franta-hg@34
   152
	public List<NamedParameter> getParameters() {
franta-hg@34
   153
		return parameters;
franta-hg@34
   154
	}
franta-hg@1
   155
}