All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.specrunner.sql.PluginScripts Maven / Gradle / Ivy

There is a newer version: 1.5.17
Show newest version
/*
    SpecRunner - Acceptance Test Driven Development Tool
    Copyright (C) 2011-2016  Thiago Santos

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see 
 */
package org.specrunner.sql;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import javax.sql.DataSource;

import org.specrunner.SRServices;
import org.specrunner.context.IBlock;
import org.specrunner.context.IContext;
import org.specrunner.features.IFeatureManager;
import org.specrunner.plugins.ActionType;
import org.specrunner.plugins.ENext;
import org.specrunner.plugins.PluginException;
import org.specrunner.plugins.core.AbstractPluginValue;
import org.specrunner.plugins.type.Command;
import org.specrunner.result.IResultSet;
import org.specrunner.result.status.Failure;
import org.specrunner.result.status.Success;
import org.specrunner.result.status.Warning;
import org.specrunner.source.ISource;
import org.specrunner.sql.util.StringUtil;
import org.specrunner.util.UtilLog;
import org.specrunner.util.cache.ICache;
import org.specrunner.util.cache.ICacheFactory;
import org.specrunner.util.expression.UtilExpression;

import nu.xom.Attribute;
import nu.xom.Element;

/**
 * This plugin specifies the scripts to be performed. Scripts are specification
 * relative, and multiple scripts can be added using multiple call or by giving
 * names separated by ';'. If you prefer different separators for scripts or
 * commands you can set 'scriptseparator', 'sqlseparator', and/or
 * 'nameseparator', or set FEATURE_SCRIPT_SEPARATOR, FEATURE_SQL_SEPARATOR or
 * FEATURE_NAME_SEPARATOR.
 * 
 * 

* If scripts are expected to be relative to classpath, set 'classpathrelative' * attribute to true, or add the 'FEATURE_CLASSPATH_RELATIVE' to true. *

* * @author Thiago Santos * */ public class PluginScripts extends AbstractPluginValue { /** * Cache of scripts. */ protected static ThreadLocal> cache = new ThreadLocal>() { @Override protected ICache initialValue() { return SRServices.get(ICacheFactory.class).newCache(PluginScripts.class.getName()); }; }; /** * Sets the script separator on specification. Default is ";". */ public static final String FEATURE_SCRIPT_SEPARATOR = PluginScripts.class.getName() + ".scriptseparator"; /** * Separator of script names. */ private String scriptseparator = ";"; /** * List of scripts to be performed. */ private URI[] scripts; /** * Sets the SQL command separator. Default is ";". */ public static final String FEATURE_SQL_SEPARATOR = PluginScripts.class.getName() + ".sqlseparator"; /** * The SQL comand separator. */ private String sqlseparator = ";"; /** * Feature for names separators. */ public static final String FEATURE_NAME_SEPARATOR = PluginScripts.class.getName() + ".separator"; /** * Default name separator. */ public static final String DEFAULT_NAME_SEPARATOR = ";"; /** * The name separator, default is ";". */ private String nameseparator = DEFAULT_NAME_SEPARATOR; /** * Feature for classpath relative scripts. */ public static final String FEATURE_CLASSPATH_RELATIVE = PluginScripts.class.getName() + ".classpath"; /** * Default classpath relative. */ public static final boolean DEFAULT_CLASSPATH_RELATIVE = false; /** * The name separator, default is ";". */ private boolean classpathrelative = DEFAULT_CLASSPATH_RELATIVE; /** * Fail safe feature means that on command execution command errors will not * raise errors but warnings instead. */ public static final String FEATURE_FAILSAFE = PluginScripts.class.getName() + ".failsafe"; /** * Set fail safe state. */ private Boolean failsafe = false; /** * List of scripts as URIs. * * @return The script list. */ public URI[] getScripts() { return scripts; } /** * Set scripts. * * @param scripts * The scripts. */ public void setScripts(URI[] scripts) { this.scripts = scripts == null ? null : Arrays.copyOf(scripts, scripts.length); } /** * Return the script separator token. * * @return The script separator. */ public String getScriptseparator() { return scriptseparator; } /** * Set script separator. * * @param scriptseparator * The separator. */ public void setScriptseparator(String scriptseparator) { this.scriptseparator = scriptseparator; } /** * The SQL commands separator. * * @return The separator. */ public String getSqlseparator() { return sqlseparator; } /** * Sets the SQL command separators. * * @param sqlseparator * Separator. */ public void setSqlseparator(String sqlseparator) { this.sqlseparator = sqlseparator; } /** * Get the name separator. * * @return The separator. */ public String getNameseparator() { return nameseparator; } /** * Set the name separator. * * @param nameseparator * The separator. */ public void setNameseparator(String nameseparator) { this.nameseparator = nameseparator; } /** * Get if script is classpath relative or not. * * @return false, if not classpath relative, true, otherwise. */ public boolean isClasspathrelative() { return classpathrelative; } /** * Set classpath relative script flag. * * @param classpathrelative * true, to set classpath relative, false, otherwise. */ public void setClasspathrelative(boolean classpathrelative) { this.classpathrelative = classpathrelative; } /** * Set if commands execution should report errors or warning on command * execution failure. * * @return True, if warnings are expected, false, otherwise. */ public Boolean getFailsafe() { return failsafe; } /** * Set fail safe status. * * @param failsafe * Fail safe status. */ public void setFailsafe(Boolean failsafe) { this.failsafe = failsafe; } @Override public ActionType getActionType() { return Command.INSTANCE; } @Override public void initialize(IContext context) throws PluginException { super.initialize(context); IFeatureManager fm = SRServices.getFeatureManager(); fm.set(FEATURE_SCRIPT_SEPARATOR, this); fm.set(FEATURE_SQL_SEPARATOR, this); fm.set(FEATURE_NAME_SEPARATOR, this); fm.set(FEATURE_CLASSPATH_RELATIVE, this); fm.set(FEATURE_FAILSAFE, this); setScripts(context); } /** * Set script, if necessary. * * @param context * The context. * @throws PluginException * On error. */ protected void setScripts(IContext context) throws PluginException { // script manually set should not be changed. if (scripts == null) { Object tmp = getValue(getValue() != null ? getValue() : context.getNode().getValue(), true, context); String value = String.valueOf(tmp); StringTokenizer st = new StringTokenizer(value, scriptseparator); List list = new LinkedList(); ISource source = context.getCurrentSource(); URI base = source.getURI(); while (st.hasMoreTokens()) { String val = st.nextToken().trim(); if (!val.isEmpty()) { URI f = null; if (classpathrelative) { try { URL resource = PluginScripts.class.getResource(val); if (resource == null) { throw new PluginException("Resource '" + val + "' not found on classpath."); } f = resource.toURI(); } catch (URISyntaxException e) { String msg = "Could not find: " + val; if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(msg); } throw new PluginException(msg, e); } } else { f = base.resolve(val); } list.add(f); if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("PluginScript scheduled:" + f); } } } scripts = list.toArray(new URI[list.size()]); } } @Override public ENext doStart(IContext context, IResultSet result) throws PluginException { String[] sources = StringUtil.tokenize(getName() != null ? getName() : PluginConnection.DEFAULT_CONNECTION_NAME, nameseparator); int failure = 0; for (String source : sources) { IDataSourceProvider provider = PluginConnection.getProvider(context, source); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("PluginScript provider:" + provider); } DataSource ds = provider.getDataSource(); Connection connection = null; try { connection = ds.getConnection(); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("PluginScript connection:" + connection); } for (URI u : scripts) { Reader reader = null; String str = u.toString(); String script = cache.get().get(str); boolean fromCache = script != null; if (fromCache) { reader = new StringReader(script); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("Script " + str + " reused."); } } else { if (str != null && str.startsWith("file:")) { try { URI uri = new URI(str); str = new File(uri).toString(); } catch (URISyntaxException e) { result.addResult(Failure.INSTANCE, context.peek(), new PluginException("Invalid script reference:" + str + ".")); failure++; continue; } File f = new File(str); if (!f.exists()) { result.addResult(Failure.INSTANCE, context.peek(), new PluginException("Script:" + f + " not found.")); failure++; } else { IBlock block = context.peek(); if (block.getNode() instanceof Element) { Element old = (Element) block.getNode(); old.addAttribute(new Attribute("href", String.valueOf(f.toURI()))); } try { reader = new FileReader(f); } catch (FileNotFoundException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } } } } else { try { URL url = u.toURL(); URLConnection con = url.openConnection(); reader = new InputStreamReader(con.getInputStream()); } catch (MalformedURLException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.peek(), e); failure++; } catch (IOException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.peek(), e); failure++; } } } if (reader != null) { if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("PluginScript perform:" + u); } failure += perform(context, result, connection, str, reader, fromCache); } } } catch (SQLException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } failure++; result.addResult(Failure.INSTANCE, context.peek(), new PluginException("Error in connection:" + source + ". Error:" + e.getMessage(), e)); } finally { try { if (connection != null) { connection.commit(); } } catch (SQLException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.peek(), new PluginException("Error in connection:" + source + ". Error:" + e.getMessage(), e)); } } } if (failure == 0) { result.addResult(Success.INSTANCE, context.peek()); } return ENext.DEEP; } /** * Perform script commands. * * @param context * The context. * @param result * The result. * @param connection * Database connection. * @param reference * Script reference as string. * @param script * Script content. * @param fromCache * Indicates if reader comes from cache or not. * @return The number of errors. * @throws SQLException * On SQL errors, only if fail safe is off. */ protected int perform(IContext context, IResultSet result, Connection connection, String reference, Reader script, boolean fromCache) throws SQLException { int failures = 0; Statement stmt = null; BufferedReader br = null; try { stmt = connection.createStatement(); try { StringBuilder command = new StringBuilder(); StringBuilder full = new StringBuilder(); br = new BufferedReader(script); String line = null; while ((line = br.readLine()) != null) { line = line.trim(); if (line.endsWith(sqlseparator)) { command.append(" "); command.append(line.substring(0, line.length() - 1)); line = ""; } if (line.isEmpty() || line.startsWith("--")) { if (command.length() > 0) { String sql = command.toString(); failures += execute(stmt, sql, context, result); if (!fromCache) { full.append(sql); full.append("\n\n"); } command.setLength(0); } continue; } if (UtilLog.LOG.isTraceEnabled()) { UtilLog.LOG.trace("Command before: " + line); } line = UtilExpression.replace(line, context, true); if (UtilLog.LOG.isTraceEnabled()) { UtilLog.LOG.trace("Command after: " + line); } command.append(" " + line); } if (command.length() > 0) { String sql = command.toString(); failures += execute(stmt, sql, context, result); if (!fromCache) { full.append(sql); full.append("\n\n"); } } if (!fromCache) { cache.get().put(reference, full.toString()); } if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("Script '" + reference + "' added to cache '" + cache.get().getName() + "'."); } } catch (Exception e) { failures++; if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.peek(), e); } finally { if (br != null) { try { br.close(); } catch (IOException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } } } } } finally { if (stmt != null) { stmt.close(); } if (script != null) { try { script.close(); } catch (IOException e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } } } } return failures; } /** * Perform a command, and return the number of errors in execution. * * @param stmt * A statement. * @param sql * A command. * @param context * A text context. * @param result * A result set. * @return The number of errors in execution. */ protected int execute(Statement stmt, String sql, IContext context, IResultSet result) { if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("Command execute: " + sql); } try { stmt.executeUpdate(sql); return 0; } catch (SQLException e) { if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("Command error: " + e.getMessage()); } if (!failsafe) { result.addResult(Failure.INSTANCE, context.peek(), e); return 1; } else { result.addResult(Warning.INSTANCE, context.peek(), e); return 0; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy