/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.eclipse.ecr.core.storage.sql.jdbc.dialect; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.ecr.core.storage.sql.Activator; import org.eclipse.ecr.core.storage.sql.jdbc.JDBCConnection; /** * A SQL statement and some optional tags that condition execution. */ public class SQLStatement { // for derby... public static final String DIALECT_WITH_NO_SEMICOLON = "noSemicolon"; /** Category pseudo-tag */ public static final String CATEGORY = "#CATEGORY:"; /** * Tags that may condition execution of the statement. */ public static class Tag { /** * Tag for a SELECT statement whose number of rows must be counted. Var * "emptyResult" is set accordingly. */ public static final String TAG_TEST = "#TEST:"; /** * Tag to only execute statement if a var is true. Var may be preceded * by ! inverse the test. */ public static final String TAG_IF = "#IF:"; public static final String VAR_EMPTY_RESULT = "emptyResult"; /** Tag: TAG_TEST, TAG_IF */ public final String key; /** The value behind a tag, used for TAG_IF */ public final String value; public Tag(String key, String value) { this.key = key; this.value = value; } } /** SQL statement */ public final String sql; /** Tags on the statement */ public final List<Tag> tags; public SQLStatement(String sql, List<Tag> tags) { this.sql = sql; this.tags = tags == null ? Collections.<Tag> emptyList() : tags; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("SQLStatement("); for (Tag tag : tags) { buf.append(tag.key); String value = tag.value; if (value != null) { buf.append(' '); buf.append(value); } buf.append(", "); } buf.append(sql); buf.append(')'); return buf.toString(); } /** * Reads SQL statements from a text file. * <p> * Statements have a category, and optional tags (that may condition * execution). * * <pre> * #CATEGORY: mycat * #TEST: * SELECT foo * from bar; * </pre> * * <pre> * #CATEGORY: mycat * #IF: emptyResult * #IF: somethingEnabled * INSERT INTO ...; * </pre> * * An empty line terminates a statement. */ public static Map<String, List<SQLStatement>> read(String filename, Map<String, List<SQLStatement>> statements) throws IOException { InputStream is = Activator.getResourceAsStream(filename); if (is == null) { throw new IOException("Cannot open: " + filename); } BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); String line; String category = null; List<Tag> tags = null; try { while ((line = reader.readLine()) != null) { if (line.startsWith(SQLStatement.CATEGORY)) { category = line.substring(SQLStatement.CATEGORY.length()).trim(); continue; } else if (line.startsWith(Tag.TAG_TEST) || line.startsWith(Tag.TAG_IF)) { String key = line.substring(0, line.indexOf(':') + 1); String value = line.substring(key.length()).trim(); if (value.length() == 0) { value = null; } if (tags == null) { tags = new LinkedList<Tag>(); } tags.add(new Tag(key, value)); continue; } else if (line.startsWith("#")) { continue; } StringBuilder buf = new StringBuilder(); boolean read = false; while (true) { if (read) { line = reader.readLine(); } else { read = true; } if (line == null || line.trim().equals("")) { if (buf.length() == 0) { break; } String sql = buf.toString().trim(); SQLStatement statement = new SQLStatement(sql, tags); List<SQLStatement> catStatements = statements.get(category); if (catStatements == null) { statements.put(category, catStatements = new LinkedList<SQLStatement>()); } catStatements.add(statement); break; } else if (line.startsWith("#")) { continue; } else { buf.append(line); buf.append('\n'); } } tags = null; if (line == null) { break; } } } finally { reader.close(); } return statements; } protected static String replaceVars(String sql, Map<String, Serializable> properties) { if (properties != null) { for (Entry<String, Serializable> en : properties.entrySet()) { String key = "${" + en.getKey() + "}"; String value = String.valueOf(en.getValue()); sql = sql.replaceAll(Pattern.quote(key), Matcher.quoteReplacement(value)); } } return sql; } /** * Executes a list of SQL statements, following the tags. */ public static void execute(List<SQLStatement> statements, Map<String, Serializable> properties, JDBCConnection jdbc) throws SQLException { Statement st = jdbc.connection.createStatement(); try { STATEMENT: // for (SQLStatement statement : statements) { boolean test = false; for (Tag tag : statement.tags) { if (tag.key.equals(Tag.TAG_TEST)) { test = true; } else if (tag.key.equals(Tag.TAG_IF)) { String key = tag.value; boolean neg = key.startsWith("!"); if (neg) { key = key.substring(1).trim(); } Serializable value = properties.get(key); if (value == null) { jdbc.logger.error("Unknown condition: " + key); continue STATEMENT; } if (!(value instanceof Boolean)) { jdbc.logger.error("Not a boolean condition: " + key); continue STATEMENT; } if (((Boolean) value).booleanValue() == neg) { // condition failed continue STATEMENT; } // ok } } String sql = statement.sql; sql = replaceVars(sql, properties); if (sql.startsWith("LOG.DEBUG")) { String msg = sql.substring("LOG.DEBUG".length()).trim(); jdbc.logger.log(msg); continue; } else if (sql.startsWith("LOG.INFO")) { String msg = sql.substring("LOG.INFO".length()).trim(); jdbc.logger.info(msg); continue; } else if (sql.startsWith("LOG.ERROR")) { String msg = sql.substring("LOG.ERROR".length()).trim(); jdbc.logger.error(msg); continue; } else if (sql.startsWith("LOG.FATAL")) { String msg = sql.substring("LOG.FATAL".length()).trim(); jdbc.logger.error(msg); throw new SQLException("Fatal error: " + msg); } jdbc.logger.log(sql.replace("\n", "\n ")); // indented if (sql.endsWith(";") && properties.containsKey(DIALECT_WITH_NO_SEMICOLON)) { // derby at least doesn't allow a terminating semicolon sql = sql.substring(0, sql.length() - 1); } try { if (test) { ResultSet rs = st.executeQuery(sql); Boolean emptyResult = Boolean.valueOf(!rs.next()); properties.put(Tag.VAR_EMPTY_RESULT, emptyResult); jdbc.logger.log(" -> emptyResult = " + emptyResult); } else { st.execute(sql); } } catch (SQLException e) { throw new SQLException("Error executing: " + sql + " : " + e.getMessage(), e); } } } finally { st.close(); } } }