001: /*
002: * VariablePool.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.sql;
013:
014: import java.io.File;
015: import java.io.IOException;
016: import java.sql.SQLException;
017: import java.sql.Types;
018: import java.util.Collections;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.Properties;
024: import java.util.Set;
025: import java.util.TreeSet;
026: import java.util.Map.Entry;
027: import java.util.regex.Matcher;
028: import java.util.regex.Pattern;
029: import workbench.db.TableIdentifier;
030: import workbench.db.WbConnection;
031: import workbench.interfaces.JobErrorHandler;
032: import workbench.log.LogMgr;
033: import workbench.resource.ResourceMgr;
034: import workbench.resource.Settings;
035: import workbench.storage.DataStore;
036: import workbench.storage.DmlStatement;
037: import workbench.storage.RowData;
038: import workbench.util.StringUtil;
039: import workbench.util.WbProperties;
040:
041: /**
042: * A class to store workbench specific variables.
043: * This is a singleton which stores the variables inside a Map.
044: * When the Pool is created it looks for any variable definition
045: * passed through the system properties.
046: * Any system property that starts with wbp. is used to define a variable.
047: * The name of the variable is the part after the <tt>wbp.</tt> prefix.
048: *
049: * @see workbench.sql.wbcommands.WbDefineVar
050: *
051: * @author support@sql-workbench.net
052: */
053: public class VariablePool {
054: public static final String PROP_PREFIX = "wbp.";
055: private Map<String, String> data = new HashMap<String, String>();
056: private static final VariablePool POOL = new VariablePool();
057: private String prefix;
058: private String suffix;
059: private int prefixLen = 0;
060: private int suffixLen = 0;
061: private Pattern validNamePattern = Pattern.compile("[\\w]*");;
062: private Pattern promptPattern;
063:
064: public static VariablePool getInstance() {
065: return POOL;
066: }
067:
068: private VariablePool() {
069: this .prefix = Settings.getInstance().getSqlParameterPrefix();
070: this .suffix = Settings.getInstance().getSqlParameterSuffix();
071:
072: if (this .suffix == null)
073: this .suffix = StringUtil.EMPTY_STRING;
074:
075: String expr = StringUtil.quoteRegexMeta(prefix)
076: + "[\\?\\&][\\w]*" + StringUtil.quoteRegexMeta(suffix);
077: this .promptPattern = Pattern.compile(expr);
078: this .initFromProperties(System.getProperties());
079: }
080:
081: void initFromProperties(Properties props) {
082: synchronized (this .data) {
083: this .data.clear();
084: Iterator itr = props.entrySet().iterator();
085: while (itr.hasNext()) {
086: Entry entry = (Entry) itr.next();
087: String key = (String) entry.getKey();
088: if (key.startsWith(PROP_PREFIX)) {
089: String varName = key
090: .substring(PROP_PREFIX.length());
091: String value = (String) entry.getValue();
092: try {
093: this .setParameterValue(varName, value);
094: } catch (IllegalArgumentException e) {
095: LogMgr.logError("SqlParameterPool.init()",
096: "Error setting variable", e);
097: }
098: }
099: }
100: }
101: }
102:
103: /**
104: * Used to reset the pool during testing
105: */
106: void clear() {
107: synchronized (this .data) {
108: this .data.clear();
109: }
110: }
111:
112: public String replacePrompts(String sql) {
113: Set<String> vars = this .getPromptVariables(sql, false);
114: return this .replaceParameters(vars, sql, true);
115: }
116:
117: public String replacePrompts(Set<String> vars, String sql) {
118: return this .replaceParameters(vars, sql, true);
119: }
120:
121: /**
122: * Returns a set of prompt variables defined in the
123: * SQL string. If a variable is not yet defined it will
124: * be created in the internal pool with an empty value.
125: * and returned in the result set.
126: *
127: * @return a Set containing variable names (String objects)
128: */
129: public Set getVariablesNeedingPrompt(String sql) {
130: return this .getPromptVariables(sql, false);
131: }
132:
133: public boolean hasPrompt(String sql) {
134: if (sql == null)
135: return false;
136: Matcher m = this .promptPattern.matcher(sql);
137: if (m == null)
138: return false;
139: return m.find();
140: }
141:
142: private Set<String> getPromptVariables(String sql,
143: boolean includeConditional) {
144: if (sql == null)
145: return Collections.emptySet();
146: Matcher m = this .promptPattern.matcher(sql);
147: if (m == null)
148: return Collections.emptySet();
149: Set<String> variables = new TreeSet<String>();
150: synchronized (this .data) {
151: while (m.find()) {
152: int start = m.start() + this .prefix.length();
153: int end = m.end() - this .suffix.length();
154: char type = sql.charAt(start);
155: String var = sql.substring(start + 1, end);
156: if (!includeConditional) {
157: if ('&' == type) {
158: String value = this .getParameterValue(var);
159: if (value != null && value.length() > 0)
160: continue;
161: }
162: }
163: variables.add(var);
164: if (!this .data.containsKey(var)) {
165: this .data.put(var, "");
166: }
167: }
168: }
169: return Collections.unmodifiableSet(variables);
170: }
171:
172: public Pattern getPromptPattern() {
173: return this .promptPattern;
174: }
175:
176: public DataStore getVariablesDataStore() {
177: return this .getVariablesDataStore(Collections
178: .synchronizedSet(this .data.keySet()));
179: }
180:
181: public DataStore getVariablesDataStore(Set varNames) {
182: DataStore vardata = new VariableDataStore();
183:
184: Iterator itr = varNames.iterator();
185: synchronized (this .data) {
186: while (itr.hasNext()) {
187: String key = (String) itr.next();
188: if (!this .data.containsKey(key))
189: continue;
190: String value = this .data.get(key);
191: int row = vardata.addRow();
192: vardata.setValue(row, 0, key);
193: vardata.setValue(row, 1, value);
194: }
195: }
196: vardata.sortByColumn(0, true);
197: vardata.resetStatus();
198: return vardata;
199: }
200:
201: public String getParameterValue(String varName) {
202: if (varName == null)
203: return null;
204: synchronized (this .data) {
205: return this .data.get(varName);
206: }
207: }
208:
209: /**
210: * Returns the number of parameters currently defined.
211: */
212: public int getParameterCount() {
213: if (this .data == null)
214: return 0;
215: synchronized (this .data) {
216: return this .data.size();
217: }
218: }
219:
220: public String replaceAllParameters(String sql) {
221: if (this .data == null || this .data.size() == 0)
222: return sql;
223: synchronized (this .data) {
224: return this .replaceParameters(this .data.keySet(), sql,
225: false);
226: }
227: }
228:
229: private String replaceParameters(Set<String> varNames, String sql,
230: boolean forPrompt) {
231: if (sql == null)
232: return null;
233: if (sql.trim().length() == 0)
234: return StringUtil.EMPTY_STRING;
235: if (sql.indexOf(this .prefix) == -1)
236: return sql;
237: StringBuilder newSql = new StringBuilder(sql);
238: for (String name : varNames) {
239: String var = this .buildVarNamePattern(name, forPrompt);
240: String value = this .data.get(name);
241: if (value == null)
242: continue;
243: replaceVarValue(newSql, var, value);
244: }
245: return newSql.toString();
246: }
247:
248: /**
249: * Replaces the variable defined through pattern with the replacement string
250: * inside the string original.
251: * String.replaceAll() cannot be used, because it parses escape sequences
252: */
253: private void replaceVarValue(StringBuilder original,
254: String pattern, String replacement) {
255: //StringBuilder result = new StringBuilder(original);
256: Pattern p = Pattern.compile(pattern);
257: Matcher m = p.matcher(original);
258: while (m != null && m.find()) {
259: int start = m.start();
260: int end = m.end();
261: original.replace(start, end, replacement);
262: m = p.matcher(original.toString());
263: }
264: //return result.toString();
265: }
266:
267: public String buildVarName(String varName, boolean forPrompt) {
268: StringBuilder result = new StringBuilder(varName.length()
269: + this .prefixLen + this .suffixLen + 1);
270: result.append(this .prefix);
271: if (forPrompt)
272: result.append('?');
273: result.append(varName);
274: result.append(this .suffix);
275: return result.toString();
276: }
277:
278: public String buildVarNamePattern(String varName, boolean forPrompt) {
279: StringBuilder result = new StringBuilder(varName.length()
280: + this .prefixLen + this .suffixLen + 1);
281:
282: result.append(StringUtil.quoteRegexMeta(prefix));
283: if (forPrompt) {
284: result.append("[\\?\\&]{1}");
285: } else {
286: result.append("[\\?\\&]?");
287: }
288: result.append(varName);
289: result.append(StringUtil.quoteRegexMeta(suffix));
290: return result.toString();
291: }
292:
293: public boolean isVariableDefined(String varName) {
294: return (getParameterValue(varName) != null);
295: }
296:
297: public boolean removeValue(String varName) {
298: if (varName == null)
299: return false;
300: synchronized (this .data) {
301: Object old = this .data.remove(varName);
302: return (old != null);
303: }
304: }
305:
306: public void setParameterValue(String varName, String value)
307: throws IllegalArgumentException {
308: if (this .isValidVariableName(varName)) {
309: synchronized (this .data) {
310: this .data.put(varName, value);
311: }
312: } else {
313: String msg = ResourceMgr
314: .getString("ErrIllegalVariableName");
315: msg = StringUtil.replace(msg, "%varname%", varName);
316: msg = msg + "\n"
317: + ResourceMgr.getString("ErrVarDefWrongName");
318: throw new IllegalArgumentException(msg);
319: }
320: }
321:
322: public boolean isValidVariableName(String varName) {
323: return this .validNamePattern.matcher(varName).matches();
324: }
325:
326: /**
327: * Initialize the variables from a commandline parameter.
328: * If the parameter starts with the # character
329: * assumed that the parameter contains a list of variable definitions
330: * enclosed in brackets. e.g.
331: * -vardef="#var1=value1,var2=value2"
332: * The list needs to be quoted on the commandline!
333: */
334: public void readDefinition(String parameter) throws Exception {
335: if (parameter == null || parameter.trim().length() == 0)
336: return;
337: if (parameter.startsWith("#")) {
338: readNameList(parameter.substring(1));
339: } else {
340: readFromFile(parameter, null);
341: }
342: }
343:
344: private void readNameList(String list) {
345: List<String> defs = StringUtil.stringToList(list, ",");
346: for (String line : defs) {
347: int pos = line.indexOf('=');
348: if (pos == -1)
349: return;
350: String key = line.substring(0, pos);
351: String value = line.substring(pos + 1);
352: try {
353: this .setParameterValue(key, value);
354: } catch (IllegalArgumentException e) {
355: LogMgr.logWarning("SqlParameterPool.readNameList()",
356: "Ignoring definition: " + line);
357: }
358: }
359: }
360:
361: /**
362: * Read the variable defintions from an external file.
363: * The file has to be a regular Java properties file, but does not support
364: * line continuation.
365: */
366: public void readFromFile(String filename, String encoding)
367: throws IOException {
368: WbProperties props = new WbProperties(this );
369: File f = new File(filename);
370: if (!f.exists())
371: return;
372:
373: props.loadTextFile(filename, encoding);
374: Iterator itr = props.entrySet().iterator();
375: while (itr.hasNext()) {
376: Entry entry = (Entry) itr.next();
377: Object key = entry.getKey();
378: Object value = entry.getValue();
379: if (key != null && value != null) {
380: this .setParameterValue((String) key, (String) value);
381: }
382: }
383: String msg = ResourceMgr.getString("MsgVarDefFileLoaded");
384: msg = StringUtil.replace(msg, "%file%", f.getAbsolutePath());
385: LogMgr.logInfo("SqlParameterPool.readFromFile", msg);
386: }
387:
388: }
389:
390: class VariableDataStore extends DataStore {
391: private static final String cols[] = {
392: ResourceMgr.getString("LblVariableName"),
393: ResourceMgr.getString("LblVariableValue") };
394: private static final int types[] = { Types.VARCHAR, Types.VARCHAR };
395: private static final int sizes[] = { 20, 50 };
396: private static final TableIdentifier TABLE_ID = new TableIdentifier(
397: "WB$VARIABLE_DEFINITION");
398:
399: public VariableDataStore() {
400: super (cols, types, sizes);
401: this .setUpdateTable(TABLE_ID);
402: }
403:
404: public List<DmlStatement> getUpdateStatements(WbConnection aConn) {
405: return Collections.emptyList();
406: }
407:
408: public boolean hasPkColumns() {
409: return true;
410: }
411:
412: public boolean checkUpdateTable(String sql, WbConnection conn) {
413: return true;
414: }
415:
416: public boolean isUpdateable() {
417: return true;
418: }
419:
420: public boolean hasUpdateableColumns() {
421: return true;
422: }
423:
424: public int updateDb(WbConnection aConnection,
425: JobErrorHandler errorHandler) throws SQLException,
426: IllegalArgumentException {
427: int rowcount = this .getRowCount();
428: this .resetUpdateRowCounters();
429:
430: VariablePool pool = VariablePool.getInstance();
431: for (int i = 0; i < rowcount; i++) {
432: String key = this .getValueAsString(i, 0);
433: String oldkey = (String) this .getOriginalValue(i, 0);
434: if (oldkey != null && !key.equals(oldkey)) {
435: pool.removeValue(oldkey);
436: }
437: String value = this .getValueAsString(i, 1);
438: pool.setParameterValue(key, value);
439: }
440:
441: RowData row = this .getNextDeletedRow();
442: while (row != null) {
443: String key = (String) row.getValue(0);
444: pool.removeValue(key);
445: row = this.getNextDeletedRow();
446: }
447: this.resetStatus();
448: return rowcount;
449: }
450:
451: }
|