org.exist.xquery.TryCatchExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exist-core Show documentation
Show all versions of exist-core Show documentation
eXist-db NoSQL Database Core
/*
* eXist Open Source Native XML Database
* Copyright (C) 2010 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id: TryCatchExpression.java 13700 2011-01-30 13:34:44Z dizzzz $
*/
package org.exist.xquery;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.xerces.impl.xpath.XPath;
import org.exist.Namespaces;
import org.exist.dom.QName;
import org.exist.xquery.ErrorCodes.ErrorCode;
import org.exist.xquery.ErrorCodes.JavaErrorCode;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.*;
/**
* XQuery 3.0 try {...} catch{...} expression.
*
* @author Adam Retter
* @author Leif-Jöran Olsson
* @author Dannes Wessels
*/
public class TryCatchExpression extends AbstractExpression {
private static final Logger LOG = LogManager.getLogger(TryCatchExpression.class);
private static final QName QN_CODE = new QName("code", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_DESCRIPTION = new QName("description", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_VALUE = new QName("value", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_MODULE = new QName("module", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_LINE_NUM = new QName("line-number", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_COLUMN_NUM = new QName("column-number", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_ADDITIONAL = new QName("additional", Namespaces.W3C_XQUERY_XPATH_ERROR_NS, Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_XQUERY_STACK_TRACE = new QName("xquery-stack-trace", Namespaces.EXIST_XQUERY_XPATH_ERROR_NS, Namespaces.EXIST_XQUERY_XPATH_ERROR_PREFIX);
private static final QName QN_JAVA_STACK_TRACE = new QName("java-stack-trace", Namespaces.EXIST_XQUERY_XPATH_ERROR_NS, Namespaces.EXIST_XQUERY_XPATH_ERROR_PREFIX);
private final Expression tryTargetExpr;
private final List catchClauses = new ArrayList<>();
/**
* Constructor.
*
* @param context Xquery context
* @param tryTargetExpr Expression to be evaluated
*/
public TryCatchExpression(final XQueryContext context, final Expression tryTargetExpr) {
super(context);
this.tryTargetExpr = tryTargetExpr;
}
/**
* Receive catch-clause data from parser.
*
* TODO: check if catchVars are still needed
*
* @param catchErrorList list of errors to catch
* @param catchVars variable names for caught errors: unused (from earlier version of the spec?)
* @param catchExpr the expression to be evaluated if error is caught
*/
public void addCatchClause(final List catchErrorList, final List catchVars, final Expression catchExpr) {
catchClauses.add( new CatchClause(catchErrorList, catchVars, catchExpr) );
}
@Override
public int getDependencies() {
return Dependency.CONTEXT_SET | Dependency.CONTEXT_ITEM;
}
public Expression getTryTargetExpr() {
return tryTargetExpr;
}
public List getCatchClauses() {
return catchClauses;
}
@Override
public int getCardinality() {
return Cardinality.ZERO_OR_MORE;
}
@Override
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
final LocalVariable mark = context.markLocalVariables(false);
try {
contextInfo.setFlags(contextInfo.getFlags() & (~IN_PREDICATE));
contextInfo.setParent(this);
context.declareVariableBinding(new LocalVariable(QN_ADDITIONAL));
context.declareVariableBinding(new LocalVariable(QN_COLUMN_NUM));
context.declareVariableBinding(new LocalVariable(QN_LINE_NUM));
context.declareVariableBinding(new LocalVariable(QN_CODE));
context.declareVariableBinding(new LocalVariable(QN_DESCRIPTION));
context.declareVariableBinding(new LocalVariable(QN_MODULE));
context.declareVariableBinding(new LocalVariable(QN_VALUE));
context.declareVariableBinding(new LocalVariable(QN_JAVA_STACK_TRACE));
context.declareVariableBinding(new LocalVariable(QN_XQUERY_STACK_TRACE));
tryTargetExpr.analyze(contextInfo);
for (final CatchClause catchClause : catchClauses) {
catchClause.getCatchExpr().analyze(contextInfo);
}
} finally {
// restore the local variable stack
context.popLocalVariables(mark);
}
}
@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
context.expressionStart(this);
if(getContext().getXQueryVersion()<30){
throw new XPathException(this, ErrorCodes.EXXQDY0003, "The try-catch expression is only available in xquery version \"3.0\" and later.");
}
try {
// Evaluate 'try' expression
final Sequence tryTargetSeq = tryTargetExpr.eval(contextSequence, contextItem);
return tryTargetSeq;
} catch (final Throwable throwable) {
final ErrorCode errorCode;
// fn:error throws an XPathException
if(throwable instanceof XPathException){
// Get errorcode from nicely thrown xpathexception
final XPathException xpe = (XPathException)throwable;
if(xpe.getErrorCode() != null) {
if(xpe.getErrorCode() == ErrorCodes.ERROR) {
errorCode = extractErrorCode(xpe);
} else {
errorCode = xpe.getErrorCode();
}
} else {
// if no errorcode is found, reconstruct by parsing the error text.
errorCode = extractErrorCode(xpe);
}
} else {
// Get errorcode from all other errors and exceptions
errorCode = new JavaErrorCode(throwable);
}
// We need the qname in the end
final QName errorCodeQname = errorCode.getErrorQName();
// Exception in thrown, catch expression will be evaluated.
// catchvars (CatchErrorCode (, CatchErrorDesc (, CatchErrorVal)?)? )
// need to be retrieved as variables
Sequence catchResultSeq = null;
final LocalVariable mark0 = context.markLocalVariables(false); // DWES: what does this do?
// DWES: should I use popLocalVariables
context.declareInScopeNamespace(Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX, Namespaces.W3C_XQUERY_XPATH_ERROR_NS);
context.declareInScopeNamespace(Namespaces.EXIST_XQUERY_XPATH_ERROR_PREFIX, Namespaces.EXIST_XQUERY_XPATH_ERROR_NS);
//context.declareInScopeNamespace(null, null);
try {
// flag used to escape loop when errorcode has matched
boolean errorMatched = false;
// Iterate on all catch clauses
for (final CatchClause catchClause : catchClauses) {
if (isErrorInList(errorCodeQname, catchClause.getCatchErrorList()) && !errorMatched) {
errorMatched = true;
// Get catch variables
final LocalVariable mark1 = context.markLocalVariables(false); // DWES: what does this do?
try {
// Add std errors
addErrCode(errorCodeQname);
addErrDescription(throwable, errorCode);
addErrValue(throwable);
addErrModule(throwable);
addErrLineNumber(throwable);
addErrColumnNumber(throwable);
addErrAdditional(throwable);
addFunctionTrace(throwable);
addJavaTrace(throwable);
// Evaluate catch expression
catchResultSeq = ((Expression) catchClause.getCatchExpr()).eval(contextSequence, contextItem);
} finally {
context.popLocalVariables(mark1, catchResultSeq);
}
} else {
// if in the end nothing is set, rethrow after loop
}
} // for catch clauses
// If an error hasn't been caught, throw new one
if (!errorMatched) {
if (throwable instanceof XPathException) {
throw throwable;
} else {
LOG.error(throwable);
throw new XPathException(throwable);
}
}
} finally {
context.popLocalVariables(mark0, catchResultSeq);
}
return catchResultSeq;
} finally {
context.expressionEnd(this);
}
}
// err:additional item()*
// Implementation-defined. This variable must be bound so that a query
// can reference it without raising an error. The purpose of this
// variable is to allow implementations to provide any additional
// information that might be useful.
private void addErrAdditional(final Throwable t) throws XPathException {
final LocalVariable err_additional = new LocalVariable(QN_ADDITIONAL);
err_additional.setSequenceType(new SequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE));
err_additional.setValue(Sequence.EMPTY_SEQUENCE);
context.declareVariableBinding(err_additional);
}
// err:column-number xs:integer?
// The column number within the stylesheet module of the instruction
// where the error occurred, or an empty sequence if the information
// is not available. The value may be approximate.
private void addErrColumnNumber(final Throwable t) throws XPathException {
final LocalVariable err_column_nr = new LocalVariable(QN_COLUMN_NUM);
err_column_nr.setSequenceType(new SequenceType(Type.INTEGER, Cardinality.ZERO_OR_ONE));
final Sequence colNum;
if (t != null && t instanceof XPathException) {
colNum = new IntegerValue(((XPathException)t).getColumn());
} else {
colNum = Sequence.EMPTY_SEQUENCE;
}
err_column_nr.setValue(colNum);
context.declareVariableBinding(err_column_nr);
}
// err:line-number xs:integer?
// The line number within the stylesheet module of the instruction
// where the error occurred, or an empty sequence if the information
// is not available. The value may be approximate.
private void addErrLineNumber(final Throwable t) throws XPathException {
final LocalVariable err_line_nr = new LocalVariable(QN_LINE_NUM);
err_line_nr.setSequenceType(new SequenceType(Type.INTEGER, Cardinality.ZERO_OR_ONE));
final Sequence lineNum;
if (t != null && t instanceof XPathException) {
lineNum = new IntegerValue(((XPathException)t).getLine());
} else {
lineNum = Sequence.EMPTY_SEQUENCE;
}
err_line_nr.setValue(lineNum);
context.declareVariableBinding(err_line_nr);
}
// err:module xs:string?
// The URI (or system ID) of the module containing the expression
// where the error occurred, or an empty sequence if the information
// is not available.
private void addErrModule(final Throwable t) throws XPathException {
final LocalVariable err_module = new LocalVariable(QN_MODULE);
err_module.setSequenceType(new SequenceType(Type.STRING, Cardinality.ZERO_OR_ONE));
final Sequence module;
if (t != null && t instanceof XPathException && ((XPathException)t).getSource() != null) {
module = new StringValue(((XPathException)t).getSource().path());
} else {
module = Sequence.EMPTY_SEQUENCE;
}
err_module.setValue(module);
context.declareVariableBinding(err_module);
}
// err:value item()*
// Value associated with the error. For an error raised by calling
// the error function, this is the value of the third argument
// (if supplied).
private void addErrValue(final Throwable t) throws XPathException {
final LocalVariable err_value = new LocalVariable(QN_VALUE);
err_value.setSequenceType(new SequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE));
final Sequence errorValue;
if (t != null) {
// Get error value from exception
if(t instanceof XPathException && ((XPathException)t).getErrorVal() != null) {
errorValue = ((XPathException)t).getErrorVal();
} else {
errorValue = Sequence.EMPTY_SEQUENCE;
}
} else {
// fill data from throwable object
errorValue = null;
}
err_value.setValue(errorValue);
context.declareVariableBinding(err_value);
}
// err:description xs:string?
// A description of the error condition; an empty sequence if no
// description is available (for example, if the error function
// was called with one argument).
private void addErrDescription(final Throwable t, final ErrorCode errorCode) throws XPathException {
final Optional errorDesc = Optional.ofNullable(errorCode.getDescription());
final Optional throwableDesc = Optional.ofNullable(t instanceof XPathException ? ((XPathException) t).getDetailMessage() : t.getMessage());
final Sequence description = errorDesc
.map(
d -> new StringValue(throwableDesc.filter(td -> !td.equals(d)).map(td -> d + (d.endsWith(".") ? " " : ". ") + td).orElse(d))
).orElse(
errorDesc.map(StringValue::new).orElse(Sequence.EMPTY_SEQUENCE)
);
final LocalVariable err_description = new LocalVariable(QN_DESCRIPTION);
err_description.setSequenceType(new SequenceType(Type.QNAME, Cardinality.ZERO_OR_ONE));
err_description.setValue(description);
context.declareVariableBinding(err_description);
}
// err:code xs:QName
// The error code
private void addErrCode(final QName errorCodeQname) throws XPathException {
final LocalVariable err_code = new LocalVariable(QN_CODE);
err_code.setSequenceType(new SequenceType(Type.QNAME, Cardinality.EXACTLY_ONE));
err_code.setValue(new QNameValue(context, errorCodeQname));
context.declareVariableBinding(err_code);
}
@Override
public void dump(final ExpressionDumper dumper) {
dumper.display("try {");
dumper.startIndent();
tryTargetExpr.dump(dumper);
dumper.endIndent();
for (final CatchClause catchClause : catchClauses) {
final Expression catchExpr = (Expression) catchClause.getCatchExpr();
dumper.nl().display("} catch (expr) {");
dumper.startIndent();
catchExpr.dump(dumper);
dumper.nl().display("}");
dumper.endIndent();
}
}
/**
* Extract and construct errorcode from error text.
*/
private ErrorCode extractErrorCode(final XPathException xpe) {
// Get message from string
final String message = xpe.getMessage();
// if the 9th position has a ":" it is probably a custom error text
if (':' == message.charAt(8)) {
final String[] data = extractLocalName(xpe.getMessage());
final ErrorCode errorCode = new ErrorCode(data[0], data[1]);
LOG.debug("Parsed string '" + xpe.getMessage() + "' for Errorcode. "
+ "Qname='" + data[0] + "' message='" + data[1] + "'");
return errorCode;
}
// Convert xpe to Throwable
Throwable retVal = xpe;
// Swap with cause if present
Throwable cause = xpe.getCause();
if(cause != null && !(cause instanceof XPathException) ){
retVal = cause;
}
// Fallback, create java error
return new ErrorCodes.JavaErrorCode(retVal);
}
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
result.append("try { ");
result.append(tryTargetExpr.toString());
for (final CatchClause catchClause : catchClauses) {
final Expression catchExpr = (Expression) catchClause.getCatchExpr();
result.append(" } catch (expr) { ");
result.append(catchExpr.toString());
result.append("}");
}
return result.toString();
}
/* (non-Javadoc)
* @see org.exist.xquery.Expression#returnsType()
*/
@Override
public int returnsType() {
// fixme! /ljo
return ((Expression) catchClauses.get(0).getCatchExpr()).returnsType();
}
/* (non-Javadoc)
* @see org.exist.xquery.AbstractExpression#resetState()
*/
@Override
public void resetState(final boolean postOptimization) {
super.resetState(postOptimization);
tryTargetExpr.resetState(postOptimization);
for (final CatchClause catchClause : catchClauses) {
final Expression catchExpr = (Expression) catchClause.getCatchExpr();
catchExpr.resetState(postOptimization);
}
}
@Override
public void accept(final ExpressionVisitor visitor) {
visitor.visitTryCatch(this);
}
/**
* Check if error parameter matches list of error names.
* An '*' matches immediately.
*
* @return TRUE is qname is in list, or list contains '*', else FALSE,
*/
private boolean isErrorInList(final QName error, final List errors) {
for (final QName lError : errors) {
if (error.matches(lError)) {
return true;
}
}
return false;
}
private String[] extractLocalName(final String errorText)
throws IllegalArgumentException {
final int p = errorText.indexOf(':');
if (p == Constants.STRING_NOT_FOUND) {
return new String[]{null, errorText};
}
return new String[]{errorText.substring(0, p).trim(), errorText.substring(p + 1).trim()};
}
/**
* Write stacktrace to String.
*/
private String getStackTrace(final Throwable t ) throws IOException {
if (t == null) {
return null;
}
try(final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw)) {
t.printStackTrace(pw);
pw.flush();
return sw.toString();
}
}
private void addFunctionTrace(final Throwable t) throws XPathException {
final LocalVariable localVar = new LocalVariable(QN_XQUERY_STACK_TRACE);
localVar.setSequenceType(new SequenceType(Type.STRING, Cardinality.ZERO_OR_MORE));
final Sequence trace;
if(t != null && t instanceof XPathException) {
final List callStack = ((XPathException)t).getCallStack();
if(callStack == null){
trace = Sequence.EMPTY_SEQUENCE;
} else {
final Sequence result = new ValueSequence();
for(final XPathException.FunctionStackElement elt : callStack){
result.add(new StringValue("at " + elt.toString()) );
}
trace = result;
}
} else {
trace = Sequence.EMPTY_SEQUENCE;
}
localVar.setValue(trace);
context.declareVariableBinding(localVar);
}
private void addJavaTrace(final Throwable t) throws XPathException {
final LocalVariable localVar = new LocalVariable(QN_JAVA_STACK_TRACE);
localVar.setSequenceType(new SequenceType(Type.QNAME, Cardinality.ZERO_OR_MORE));
final Sequence trace;
if (t != null && t.getStackTrace() != null) {
final Sequence result = new ValueSequence();
addJavaTrace(t, result);
trace = result;
} else {
trace = Sequence.EMPTY_SEQUENCE;
}
localVar.setValue(trace);
context.declareVariableBinding(localVar);
}
// Local recursive function
private void addJavaTrace(final Throwable t, final Sequence result) throws XPathException {
final StackTraceElement[] elements = t.getStackTrace();
result.add(new StringValue("Caused by: " + t.toString()));
for (final StackTraceElement elt : elements) {
result.add(new StringValue("at " + elt.toString()));
}
final Throwable cause = t.getCause();
if (cause != null) {
addJavaTrace(cause, result);
}
}
/**
* Data container
*
* TODO: catchVars is unused? Remove?
*/
public static class CatchClause {
private final List catchErrorList;
private final List catchVars;
private final Expression catchExpr;
public CatchClause(final List catchErrorList, final List catchVars, final Expression catchExpr) {
this.catchErrorList = catchErrorList;
this.catchVars = catchVars;
this.catchExpr = catchExpr;
}
public List getCatchErrorList() {
return catchErrorList;
}
public Expression getCatchExpr() {
return catchExpr;
}
public List getCatchVars() {
return catchVars;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy