/*
* (c) Copyright 2009-2011 by Volker Bergmann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted under the terms of the
* GNU General Public License.
*
* For redistributing this software or a derivative work under a license other
* than the GPL-compatible Free Software License as defined by the Free
* Software Foundation or approved by OSI, you must first obtain a commercial
* license to this software product from Volker Bergmann.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
* REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
* HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.databene.benerator.engine.statement;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Map.Entry;
import org.databene.benerator.StorageSystem;
import org.databene.benerator.engine.BeneratorContext;
import org.databene.benerator.engine.Statement;
import org.databene.commons.Assert;
import org.databene.commons.BeanUtil;
import org.databene.commons.ConfigurationError;
import org.databene.commons.Context;
import org.databene.commons.ConversionException;
import org.databene.commons.ErrorHandler;
import org.databene.commons.IOUtil;
import org.databene.commons.LogCategories;
import org.databene.commons.ReaderLineIterator;
import org.databene.commons.ShellUtil;
import org.databene.commons.StringUtil;
import org.databene.commons.Level;
import org.databene.commons.SystemInfo;
import org.databene.commons.converter.LiteralParser;
import org.databene.jdbacl.DBExecutionResult;
import org.databene.jdbacl.DBUtil;
import org.databene.platform.db.DBSystem;
import org.databene.script.Expression;
import org.databene.script.Script;
import org.databene.script.ScriptUtil;
import org.databene.script.expression.ExpressionUtil;
import org.databene.task.TaskException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Executes an <evaluate/> from an XML descriptor.<br/>
* <br/>
* Created at 23.07.2009 17:59:36
* @since 0.6.0
* @author Volker Bergmann
*/
public class EvaluateStatement implements Statement {
private static final Logger logger = LoggerFactory.getLogger(EvaluateStatement.class);
private static final String SHELL = "shell";
private static final Map<String, String> extensionMap;
static {
try {
extensionMap = IOUtil.readProperties("org/databene/benerator/engine/statement/fileTypes.properties");
} catch (IOException e) {
throw new ConfigurationError(e);
}
}
boolean evaluate;
Expression<String> idEx;
Expression<String> textEx;
Expression<String> uriEx;
Expression<String> typeEx;
Expression<?> targetObjectEx;
Expression<Character> separatorEx;
Expression<String> onErrorEx;
Expression<String> encodingEx;
Expression<Boolean> optimizeEx;
Expression<Boolean> invalidateEx;
Expression<?> assertionEx;
public EvaluateStatement(boolean evaluate, Expression<String> idEx, Expression<String> textEx,
Expression<String> uriEx, Expression<String> typeEx, Expression<?> targetObjectEx,
Expression<Character> separatorEx, Expression<String> onErrorEx, Expression<String> encodingEx,
Expression<Boolean> optimizeEx, Expression<Boolean> invalidateEx, Expression<?> assertionEx) {
this.evaluate = evaluate;
this.idEx = idEx;
this.textEx = textEx;
this.uriEx = uriEx;
this.typeEx = typeEx;
this.targetObjectEx = targetObjectEx;
this.separatorEx = separatorEx;
this.onErrorEx = onErrorEx;
this.encodingEx = encodingEx;
this.optimizeEx = optimizeEx;
this.invalidateEx = invalidateEx;
this.assertionEx = assertionEx;
}
public Expression<String> getTextEx() {
return textEx;
}
public boolean execute(BeneratorContext context) {
try {
String onErrorValue = ExpressionUtil.evaluate(onErrorEx, context);
if (onErrorValue == null)
onErrorValue = "fatal";
String typeValue = ExpressionUtil.evaluate(typeEx, context);
// if type is not defined, derive it from the file extension
String uriValue = ExpressionUtil.evaluate(uriEx, context);
if (typeEx == null && uriEx != null) {
typeValue = mapExtensionOf(uriValue);
if ("winshell".equals(typeValue))
if (!SystemInfo.isWindows())
throw new ConfigurationError("Need Windows to run file: " + uriValue);
else
typeValue = SHELL;
else if ("unixshell".equals(typeValue))
if (SystemInfo.isWindows())
throw new ConfigurationError("Need Unix system to run file: " + uriValue);
else
typeValue = SHELL;
}
if (uriValue != null)
uriValue = context.resolveRelativeUri(uriValue);
Object targetObject = ExpressionUtil.evaluate(targetObjectEx, context);
if (typeValue == null && targetObject instanceof DBSystem)
typeValue = "sql";
if (typeValue == null && targetObject instanceof StorageSystem)
typeValue = "execute";
String encoding = ExpressionUtil.evaluate(encodingEx, context);
// run
Object result = null;
String text = ExpressionUtil.evaluate(textEx, context);
if ("sql".equals(typeValue)) {
Character separator = ExpressionUtil.evaluate(separatorEx, context);
if (separator == null)
separator = ';';
result = runSql(uriValue, targetObject, onErrorValue, encoding,
text, separator,
optimizeEx.evaluate(context), invalidateEx.evaluate(context)).result;
} else if (SHELL.equals(typeValue)) {
result = runShell(uriValue, text, onErrorValue);
} else if ("execute".equals(typeValue)) {
result = ((StorageSystem) targetObject).execute(text);
} else {
if (typeValue == null)
typeValue = context.getDefaultScript();
if (!StringUtil.isEmpty(uriValue))
text = IOUtil.getContentOfURI(uriValue);
result = runScript(text, typeValue, onErrorValue, context);
}
context.set("result", result);
Object assertionValue = ExpressionUtil.evaluate(assertionEx, context);
if (assertionValue instanceof String)
assertionValue = LiteralParser.parse((String) assertionValue);
if (assertionValue != null && !(assertionValue instanceof String && ((String) assertionValue).length() == 0)) {
if (assertionValue instanceof Boolean) {
if (!(Boolean) assertionValue)
getErrorHandler(onErrorValue).handleError("Assertion failed: '" + assertionEx + "'");
} else {
if (!BeanUtil.equalsIgnoreType(assertionValue, result))
getErrorHandler(onErrorValue).handleError("Assertion failed. Expected: '" + assertionValue + "', found: '" + result + "'");
}
}
String idValue = ExpressionUtil.evaluate(idEx, context);
if (idValue != null)
context.set(idValue, result);
return true;
} catch (ConversionException e) {
throw new ConfigurationError(e);
} catch (IOException e) {
throw new ConfigurationError(e);
}
}
private String mapExtensionOf(String uri) {
String lcUri = uri.toLowerCase();
for (Entry<String, String> entry : extensionMap.entrySet())
if (lcUri.endsWith(entry.getKey()))
return entry.getValue();
return null;
}
private ErrorHandler getErrorHandler(String level) {
ErrorHandler errorHandler = new ErrorHandler(getClass().getName(), Level.valueOf(level));
return errorHandler;
}
private Object runScript(String text, String type, String onError, Context context) {
ErrorHandler errorHandler = new ErrorHandler(getClass().getName(),
Level.valueOf(onError));
try {
Script script = ScriptUtil.parseScriptText(text, type);
return script.evaluate(context);
} catch (Exception e) {
errorHandler.handleError("Error in script evaluation", e);
return null;
}
}
private Object runShell(String uri, String text, String onError) {
ErrorHandler errorHandler = new ErrorHandler(getClass().getName(), Level.valueOf(onError));
StringWriter writer = new StringWriter();
if (text != null)
ShellUtil.runShellCommands(new ReaderLineIterator(new StringReader(text)), writer, errorHandler);
else if (uri != null)
ShellUtil.runShellCommand(uri, writer, errorHandler);
else
throw new ConfigurationError("At least uri or text must be provided in <execute> and <evaluate>");
String output = writer.toString();
System.out.println(output);
return LiteralParser.parse(output);
}
private DBExecutionResult runSql(String uri, Object targetObject, String onError,
String encoding, String text, char separator, boolean optimize, Boolean invalidate) {
if (targetObject == null)
throw new ConfigurationError("Please specify the 'target' database to execute the SQL script");
Assert.instanceOf(targetObject, DBSystem.class, "target");
DBSystem db = (DBSystem) targetObject;
if (uri != null)
logger.info("Executing script " + uri);
else if (text != null)
logger.info("Executing inline script");
else
throw new TaskException("No uri or content");
Connection connection = null;
DBExecutionResult result = null;
ErrorHandler errorHandler = new ErrorHandler(LogCategories.SQL, Level.valueOf(onError));
try {
connection = db.getConnection();
if (text != null)
result = DBUtil.executeScript(text, separator, connection, optimize, errorHandler);
else
result = DBUtil.executeScriptFile(uri, encoding, separator, connection, optimize, errorHandler);
if (Boolean.TRUE.equals(invalidate) || (invalidate == null && !evaluate && result.changedStructure))
db.invalidate(); // possibly we changed the database structure
} catch (Exception sqle) {
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e) {
// ignore this 2nd exception, we have other problems now (sqle)
}
}
errorHandler.handleError("Error in SQL script execution", sqle);
}
return result;
}
}