/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist.sourceforge.net * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.xquery; import com.sun.xacml.ctx.RequestCtx; import org.exist.dom.DocumentSet; import org.exist.dom.QName; import org.exist.dom.VirtualNodeSet; import org.exist.security.PermissionDeniedException; import org.exist.security.xacml.ExistPDP; import org.exist.xquery.util.Error; import org.exist.xquery.value.Item; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceType; import org.exist.xquery.value.Type; import java.util.*; /** * Represents a call to a user-defined function * {@link org.exist.xquery.UserDefinedFunction}. * * FunctionCall wraps around a user-defined function. It makes sure that all function parameters * are checked against the signature of the function. * * @author wolf */ public class FunctionCall extends Function { private UserDefinedFunction functionDef; private Expression expression; // the name of the function. Used for forward references. private QName name = null; private List arguments = null; private boolean isRecursive = false; private VariableReference varDeps[]; public FunctionCall(XQueryContext context, QName name, List arguments) { super(context); this.name = name; this.arguments = arguments; } public FunctionCall(XQueryContext context, UserDefinedFunction functionDef) { super(context); setFunction(functionDef); } private void setFunction(UserDefinedFunction functionDef) { this.functionDef = functionDef; this.mySignature = functionDef.getSignature(); this.expression = functionDef; SequenceType returnType = functionDef.getSignature().getReturnType(); // add return type checks if(returnType.getCardinality() != Cardinality.ZERO_OR_MORE) expression = new DynamicCardinalityCheck(context, returnType.getCardinality(), expression, new Error(Error.FUNC_RETURN_CARDINALITY)); if(Type.subTypeOf(returnType.getPrimaryType(), Type.ATOMIC)) expression = new Atomize(context, expression); if(Type.subTypeOf(returnType.getPrimaryType(), Type.NUMBER)) expression = new UntypedValueCheck(context, returnType.getPrimaryType(), expression, new Error(Error.FUNC_RETURN_TYPE)); else if(returnType.getPrimaryType() != Type.ITEM) expression = new DynamicTypeCheck(context, returnType.getPrimaryType(), expression); } /** * For calls to functions in external modules, check that the instance of the function we were * bound to matches the current implementation of the module bound to our context. If not, * rebind to the correct instance, but don't bother resetting the signature since it's guaranteed * (I hope!) to be the same. */ private void updateFunction() { if (functionDef.getContext() instanceof ModuleContext) { ModuleContext modContext = (ModuleContext) functionDef.getContext(); // util:eval will stuff non-module function declarations into a module context sometimes, // so watch out for those and ignore them. if (functionDef.getName().getNamespaceURI().equals(modContext.getModuleNamespace()) && modContext.getRootContext() != context.getRootContext()) { ExternalModule rootModule = (ExternalModule) context.getRootModule(functionDef.getName().getNamespaceURI()); if (rootModule != null) { UserDefinedFunction replacementFunctionDef = rootModule.getFunction(functionDef.getName(), getArgumentCount()); if (replacementFunctionDef != null) expression = functionDef = replacementFunctionDef; } } } } /* (non-Javadoc) * @see org.exist.xquery.Function#analyze(org.exist.xquery.AnalyzeContextInfo) */ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { updateFunction(); contextInfo.setParent(this); AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo); newContextInfo.removeFlag(IN_NODE_CONSTRUCTOR); super.analyze(newContextInfo); if (context.tailRecursiveCall(functionDef.getSignature())) { isRecursive = true; } context.functionStart(functionDef.getSignature()); try { expression.analyze(newContextInfo); } finally { context.functionEnd(); } varDeps = new VariableReference[getArgumentCount()]; for(int i = 0; i < getArgumentCount(); i++) { Expression arg = getArgument(i); VariableReference varRef = BasicExpressionVisitor.findVariableRef(arg); if (varRef != null) { varDeps[i] = varRef; } } } /** * Called by {@link XQueryContext} to resolve a call to a function that has not * yet been declared. XQueryContext remembers all calls to undeclared functions * and tries to resolve them after parsing has completed. * * @param functionDef * @throws XPathException */ public void resolveForwardReference(UserDefinedFunction functionDef) throws XPathException { setFunction(functionDef); setArguments(arguments); arguments = null; name = null; } public int getArgumentCount() { if (arguments == null) return super.getArgumentCount(); else return arguments.size(); } public QName getQName() { return name; } /** * Evaluates all arguments, then forwards them to the user-defined function. * * The return value of the user-defined function will be checked against the * provided function signature. * * @see org.exist.xquery.Expression#eval(Sequence, Item) */ public Sequence eval( Sequence contextSequence, Item contextItem) throws XPathException { Sequence[] seq = new Sequence[getArgumentCount()]; DocumentSet[] contextDocs = new DocumentSet[getArgumentCount()]; for(int i = 0; i < getArgumentCount(); i++) { try { seq[i] = getArgument(i).eval(contextSequence, contextItem); if (varDeps != null && varDeps[i] != null) { Variable var = varDeps[i].getVariable(); if (var != null) contextDocs[i] = var.getContextDocs(); } // System.out.println("found " + seq[i].getLength() + " for " + getArgument(i).pprint()); } catch (XPathException e) { if(e.getLine() == 0) { e.setLocation(line, column); } // append location of the function call to the exception message: e.addFunctionCall(functionDef, this); throw e; } } Sequence result = evalFunction(contextSequence, contextItem, seq, contextDocs); try { //Don't check deferred calls : it would result in a stack overflow //TODO : find a solution or... is it already here ? if (!(result instanceof DeferredFunctionCall) && //Don't test on empty sequences since they can have several types //TODO : add a prior cardinality check on wether an empty result is allowed or not //TODO : should we introduce a deffered type check on VirtualNodeSet // and trigger it when the nodeSet is realized ? !(result instanceof VirtualNodeSet) && !result.isEmpty()) getSignature().getReturnType().checkType(result.getItemType()); } catch (XPathException e) { throw new XPathException(this, "err:XPTY0004: return type of function '" + getSignature().getName() + "'. " + e.getMessage(), e); } return result; } /** * @param contextSequence * @param contextItem * @param seq * @throws XPathException */ public Sequence evalFunction(Sequence contextSequence, Item contextItem, Sequence[] seq) throws XPathException { return evalFunction(contextSequence, contextItem, seq, null); } public Sequence evalFunction(Sequence contextSequence, Item contextItem, Sequence[] seq, DocumentSet[] contextDocs) throws XPathException { if (context.isProfilingEnabled()) { context.getProfiler().start(this); context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies())); if (contextSequence != null) context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence); if (contextItem != null) context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence()); } //check access to the method try { ExistPDP pdp = context.getPDP(); if(pdp != null) { RequestCtx request = pdp.getRequestHelper().createFunctionRequest(context, null, getName()); //if request is null, this function belongs to a main module and is allowed to be called //otherwise, the access must be checked if(request != null) pdp.evaluate(request); } } catch (PermissionDeniedException pde) { XPathException xe = new XPathException(this, "Access to function '" + getName() + "' denied.", pde); xe.addFunctionCall(functionDef, this); throw xe; } functionDef.setArguments(seq, contextDocs); if (isRecursive) { // LOG.warn("Tail recursive function: " + functionDef.getSignature().toString()); return new DeferredFunctionCallImpl(functionDef.getSignature(), contextSequence, contextItem); } else { //XXX: should we have it? org.exist.xquery.UserDefinedFunction do a call -shabanovd context.stackEnter(this); context.functionStart(functionDef.getSignature()); LocalVariable mark = context.markLocalVariables(true); try { if (context.getProfiler().traceFunctions()) context.getProfiler().traceFunctionStart(this); long start = System.currentTimeMillis(); Sequence returnSeq = expression.eval(contextSequence, contextItem); while (returnSeq instanceof DeferredFunctionCall && functionDef.getSignature().equals(((DeferredFunctionCall)returnSeq).getSignature())) { // LOG.debug("Executing function: " + functionDef.getSignature()); returnSeq = ((DeferredFunctionCall) returnSeq).execute(); } if (context.getProfiler().traceFunctions()) context.getProfiler().traceFunctionEnd(this, (System.currentTimeMillis() - start)); if (context.isProfilingEnabled()) context.getProfiler().end(this, "", returnSeq); return returnSeq; } catch(XPathException e) { if(e.getLine() == 0) e.setLocation(line, column); // append location of the function call to the exception message: e.addFunctionCall(functionDef, this); throw e; } finally { context.popLocalVariables(mark); context.functionEnd(); context.stackLeave(this); } } } /* (non-Javadoc) * @see org.exist.xquery.PathExpr#resetState() */ public void resetState(boolean postOptimization) { super.resetState(postOptimization); functionDef.resetState(postOptimization); //TODO : reset expression ? } /* (non-Javadoc) * @see org.exist.xquery.Expression#setContextDocSet(org.exist.dom.DocumentSet) */ public void setContextDocSet(DocumentSet contextSet) { super.setContextDocSet(contextSet); functionDef.setContextDocSet(contextSet); } public void accept(ExpressionVisitor visitor) { // forward to the called function for(int i = 0; i < getArgumentCount(); i++) { getArgument(i).accept(visitor); } functionDef.accept(visitor); } private class DeferredFunctionCallImpl extends DeferredFunctionCall { private Sequence contextSequence; private Item contextItem; public DeferredFunctionCallImpl(FunctionSignature signature, Sequence contextSequence, Item contextItem) { super(signature); this.contextSequence = contextSequence; this.contextItem = contextItem; } protected Sequence execute() throws XPathException { context.pushDocumentContext(); context.stackEnter(expression); context.functionStart(functionDef.getSignature()); LocalVariable mark = context.markLocalVariables(true); try { Sequence returnSeq = expression.eval(contextSequence, contextItem); // LOG.debug("Returning from execute()"); return returnSeq; } catch(XPathException e) { if(e.getLine() == 0) e.setLocation(line, column); // append location of the function call to the exception message: e.addFunctionCall(functionDef, FunctionCall.this); throw e; } finally { context.popLocalVariables(mark); context.functionEnd(); context.stackLeave(expression); context.popDocumentContext(); } } } }