/* * 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 java.util.Iterator; import java.util.List; import org.exist.Namespaces; import org.exist.dom.QName; import org.exist.xquery.functions.ExtNear; import org.exist.xquery.functions.ExtPhrase; import org.exist.xquery.parser.XQueryAST; import org.exist.xquery.value.Type; public class FunctionFactory { public static final String ENABLE_JAVA_BINDING_ATTRIBUTE = "enable-java-binding"; public static final String PROPERTY_ENABLE_JAVA_BINDING = "xquery.enable-java-binding"; public static final String DISABLE_DEPRECATED_FUNCTIONS_ATTRIBUTE = "disable-deprecated-functions"; public static final String PROPERTY_DISABLE_DEPRECATED_FUNCTIONS = "xquery.disable-deprecated-functions"; public static final boolean DISABLE_DEPRECATED_FUNCTIONS_BY_DEFAULT = false; /** * Create a function call. * * This method handles all calls to built-in or user-defined * functions. It also deals with constructor functions and * optimizes some function calls like starts-with, ends-with or * contains. */ public static Expression createFunction(XQueryContext context, XQueryAST ast, PathExpr parent, List params) throws XPathException { QName qname = null; try { qname = QName.parse(context, ast.getText(), context.getDefaultFunctionNamespace()); } catch(XPathException e) { e.setLocation(ast.getLine(), ast.getColumn()); throw e; } String local = qname.getLocalName(); String uri = qname.getNamespaceURI(); Expression step = null; if(uri.equals(Function.BUILTIN_FUNCTION_NS)) { //TODO : move to text:near() // near(node-set, string) if (local.equals("near")) { if (params.size() < 2) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017: Function near() requires two arguments"); PathExpr p1 = (PathExpr) params.get(1); if (p1.getLength() == 0) throw new XPathException(ast.getLine(), ast.getColumn(), "Second argument to near is empty"); Expression e1 = p1.getExpression(0); ExtNear near = new ExtNear(context); near.setLocation(ast.getLine(), ast.getColumn()); near.addTerm(e1); near.setPath((PathExpr) params.get(0)); if (params.size() > 2) { p1 = (PathExpr) params.get(2); if (p1.getLength() == 0) { throw new XPathException(ast.getLine(), ast.getColumn(), "Max distance argument to near is empty"); } near.setMaxDistance(p1); if (params.size() == 4) { p1 = (PathExpr) params.get(3); if (p1.getLength() == 0) { throw new XPathException(ast.getLine(), ast.getColumn(), "Min distance argument to near is empty"); } near.setMinDistance(p1); } } step = near; } // phrase(node-set, string) if (local.equals("phrase")) { if (params.size() < 2) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017: Function phrase() requires two arguments"); PathExpr p1 = (PathExpr) params.get(1); if (p1.getLength() == 0) throw new XPathException(ast.getLine(), ast.getColumn(), "Second argument to phrase is empty"); Expression e1 = p1.getExpression(0); ExtPhrase phrase = new ExtPhrase(context); phrase.setLocation(ast.getLine(), ast.getColumn()); phrase.addTerm(e1); phrase.setPath((PathExpr) params.get(0)); step = phrase; } // starts-with(node-set, string) if (local.equals("starts-with")) { if (params.size() < 2) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017: Function starts-with() requires two or three arguments"); if (params.size() > 3) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017: Function starts-with() requires two or three arguments"); PathExpr p0 = (PathExpr) params.get(0); PathExpr p1 = (PathExpr) params.get(1); if (p1.getLength() == 0) throw new XPathException(ast.getLine(), ast.getColumn(), "Second argument to starts-with is empty"); GeneralComparison op = new GeneralComparison(context, p0, p1, Constants.EQ, Constants.TRUNC_RIGHT); op.setLocation(ast.getLine(), ast.getColumn()); //TODO : not sure for parent -pb context.getProfiler().message(parent, Profiler.OPTIMIZATIONS, "OPTIMIZATION", "Rewritten start-with as a general comparison with a right truncature"); if (params.size() == 3) op.setCollation((Expression)params.get(2)); step = op; } // ends-with(node-set, string) if (local.equals("ends-with")) { if (params.size() < 2) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017 : Function ends-with() requires two or three arguments"); if (params.size() > 3) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017 : Function ends-with() requires two or three arguments"); PathExpr p0 = (PathExpr) params.get(0); PathExpr p1 = (PathExpr) params.get(1); if (p1.getLength() == 0) throw new XPathException(ast.getLine(), ast.getColumn(), "Second argument to ends-with is empty"); GeneralComparison op = new GeneralComparison(context, p0, p1, Constants.EQ, Constants.TRUNC_LEFT); //TODO : not sure for parent -pb context.getProfiler().message(parent, Profiler.OPTIMIZATIONS, "OPTIMIZATION", "Rewritten ends-with as a general comparison with a left truncature"); op.setLocation(ast.getLine(), ast.getColumn()); if (params.size() == 3) op.setCollation((Expression)params.get(2)); step = op; } // contains(node-set, string) if (local.equals("contains")) { if (params.size() < 2) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017: Function contains() requires two or three arguments"); if (params.size() > 3) throw new XPathException(ast.getLine(), ast.getColumn(), "XPST0017: Function contains() requires two or three arguments"); PathExpr p0 = (PathExpr) params.get(0); PathExpr p1 = (PathExpr) params.get(1); if (p1.getLength() == 0) throw new XPathException(ast.getLine(), ast.getColumn(), "Second argument to contains is empty"); GeneralComparison op = new GeneralComparison(context, p0, p1, Constants.EQ, Constants.TRUNC_BOTH); //TODO : not sure for parent -pb context.getProfiler().message(parent, Profiler.OPTIMIZATIONS, "OPTIMIZATION", "Rewritten contains as a general comparison with left and right truncatures"); op.setLocation(ast.getLine(), ast.getColumn()); if (params.size() == 3) op.setCollation((Expression)params.get(2)); step = op; } // Check if the namespace belongs to one of the schema namespaces. // If yes, the function is a constructor function } else if(uri.equals(Namespaces.SCHEMA_NS) || uri.equals(Namespaces.XPATH_DATATYPES_NS)) { if(params.size() != 1) throw new XPathException(ast.getLine(), ast.getColumn(), "Wrong number of arguments for constructor function"); PathExpr arg = (PathExpr)params.get(0); int code= Type.getType(qname); CastExpression castExpr = new CastExpression(context, arg, code, Cardinality.ZERO_OR_ONE); castExpr.setLocation(ast.getLine(), ast.getColumn()); step = castExpr; // Check if the namespace URI starts with "java:". If yes, treat the function call as a call to // an arbitrary Java function. } else if(uri.startsWith("java:")) { //Only allow java binding if specified in config file <xquery enable-java-binding="yes"> String javabinding = (String)context.broker.getConfiguration().getProperty(PROPERTY_ENABLE_JAVA_BINDING); if(javabinding != null) { if(javabinding.equals("yes")) { JavaCall call = new JavaCall(context, qname); call.setLocation(ast.getLine(), ast.getColumn()); call.setArguments(params); step = call; } else { throw new XPathException(ast.getLine(), ast.getColumn(), "Java binding is disabled in the current configuration (see conf.xml). Call to " + qname.getStringValue() + " denied."); } } else { throw new XPathException(ast.getLine(), ast.getColumn(), "Java binding is disabled in the current configuration (see conf.xml). Call to " + qname.getStringValue() + " denied."); } } // None of the above matched: function is either a builtin function or // a user-defined function if (step == null) { Module module = context.getModule(uri); if(module != null) { // Function belongs to a module if(module.isInternalModule()) { // for internal modules: create a new function instance from the class FunctionDef def = ((InternalModule)module).getFunctionDef(qname, params.size()); if (def == null) { List funcs = ((InternalModule)module).getFunctionsByName(qname); if (funcs.size() == 0) throw new XPathException(ast.getLine(), ast.getColumn(), "Function " + qname.getStringValue() + "() " + " is not defined in module namespace: " + qname.getNamespaceURI()); else { StringBuilder buf = new StringBuilder(); buf.append("Unexpectedly received "); buf.append(params.size() + " parameter(s) in call to function "); buf.append("'" + qname.getStringValue() + "()'. "); buf.append("Defined function signatures are:\r\n"); for (Iterator i = funcs.iterator(); i.hasNext(); ) { FunctionSignature sig = (FunctionSignature) i.next(); buf.append(sig.toString()).append("\r\n"); } throw new XPathException(ast.getLine(), ast.getColumn(), buf.toString()); } } if (((Boolean)context.broker.getConfiguration().getProperty(PROPERTY_DISABLE_DEPRECATED_FUNCTIONS)).booleanValue() && def.getSignature().isDeprecated()) { throw new XPathException(ast.getLine(), ast.getColumn(), "Access to deprecated functions is not allowed. Call to '" + qname.getStringValue() + "()' denied. " + def.getSignature().getDeprecated()); } Function func = Function.createFunction(context, ast, def ); func.setArguments(params); func.setASTNode(ast); step = new InternalFunctionCall(func); } else { // function is from an imported XQuery module UserDefinedFunction func = ((ExternalModule)module).getFunction(qname, params.size()); if(func == null) { // check if the module has been compiled already if(module.isReady()) { throw new XPathException(ast.getLine(), ast.getColumn(), "err:XPST0017: Function " + qname.getStringValue() + "() is not defined in namespace '" + qname.getNamespaceURI() + "'"); } // if not, postpone the function resolution // register a forward reference with the root module, so it gets resolved // when the main query has been compiled. else { FunctionCall fc = new FunctionCall(((ExternalModule) module).getContext(), qname, params); fc.setLocation(ast.getLine(), ast.getColumn()); if(((ExternalModule) module).getContext() == context) { context.addForwardReference(fc); } else { context.getRootContext().addForwardReference(fc); } step = fc; } } else { FunctionCall fc = new FunctionCall(context, func); fc.setArguments(params); fc.setLocation(ast.getLine(), ast.getColumn()); step = fc; } } } else { UserDefinedFunction func = context.resolveFunction(qname, params.size()); FunctionCall call; if(func != null) { call = new FunctionCall(context, func); call.setLocation(ast.getLine(), ast.getColumn()); call.setArguments(params); } else { // create a forward reference which will be resolved later call = new FunctionCall(context, qname, params); call.setLocation(ast.getLine(), ast.getColumn()); context.addForwardReference(call); } step = call; } } return step; } }