[Mulgara-svn] r1809 - trunk/src/jar/query/java/org/mulgara/query/filter/value

pag at mulgara.org pag at mulgara.org
Tue Oct 6 06:41:43 UTC 2009


Author: pag
Date: 2009-10-05 23:41:42 -0700 (Mon, 05 Oct 2009)
New Revision: 1809

Modified:
   trunk/src/jar/query/java/org/mulgara/query/filter/value/ExternalFn.java
Log:
Added support for calling external functions, provided through XPathFunctionResolvers and identified by QName

Modified: trunk/src/jar/query/java/org/mulgara/query/filter/value/ExternalFn.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/filter/value/ExternalFn.java	2009-10-06 06:40:35 UTC (rev 1808)
+++ trunk/src/jar/query/java/org/mulgara/query/filter/value/ExternalFn.java	2009-10-06 06:41:42 UTC (rev 1809)
@@ -14,16 +14,26 @@
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
 
 import org.apache.log4j.Logger;
 import org.jrdf.vocabulary.RDF;
 import org.mulgara.parser.MulgaraParserException;
+import org.mulgara.query.FunctionResolverRegistry;
 import org.mulgara.query.QueryException;
 import org.mulgara.query.filter.RDFTerm;
 import org.mulgara.query.rdf.XSD;
 
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathFunction;
+import javax.xml.xpath.XPathFunctionException;
+import javax.xml.xpath.XPathFunctionResolver;
+
 /**
- * Executes a function that isn't defined in these packages.
+ * Executes a function that isn't defined in these packages. If the function is external
+ * then the answer is presumed to be scalar. URIs are automatically treated as IRIs, and anything else
+ * is treated as a literal.
  *
  * @created Apr 22, 2008
  * @author Paul Gearon
@@ -53,6 +63,15 @@
   /** This is a constructor function. */
   private boolean isConstructor = false;
 
+  /** The external function to execute. */
+  XPathFunction extFn = null;
+
+  /** Caches functions by their label of iri/#args. */
+  private static Map<String,XPathFunction> fnCache = new WeakHashMap<String,XPathFunction>();
+
+  /** Indicates that an error will always be returned from this method. */
+  private boolean unrecoverableError = false;
+
   /**
    * Create a new function instance.
    * @param fn The function to run.
@@ -65,7 +84,8 @@
       if (operands.length != 1) throw new MulgaraParserException("Cast operation can only take a single parameter");
       isConstructor = true;
     } else {
-      logger.error("Unknown function URI: " + fn);
+      extFn = findFunction(fn, operands.length);
+      if (extFn == null) logger.error("Unknown function: " + fn);
     }
   }
 
@@ -140,6 +160,12 @@
     return getContextOwner() == null ? true : resolve().isLiteral();
   }
 
+  /** @see java.lang.Object#toString() */
+  public String toString() {
+    String result = "function[" + fnUri + "]";
+    if (extFn != null) result += " -> " + extFn.getClass().getName();
+    return result;
+  }
 
   /**
    * Resolve the value of the function.
@@ -156,8 +182,24 @@
       if (XSD.isNumericType(fnUri) && value instanceof Number) return new NumericLiteral(NumericLiteral.getValueFor((Number)value, fnUri), fnUri);
       return TypedLiteral.newLiteral(value.toString(), fnUri, null);
     }
-    logger.warn("Attempting to execute an unsupported function: " + fnUri + "(" + resolveArgs() + ")");
-    return Bool.TRUE;
+    if (extFn == null) {
+      logger.debug("Attempting to execute an unsupported function: " + fnUri + "(" + resolveArgs() + ")");
+      return Bool.FALSE;
+    }
+    if (unrecoverableError) return Bool.FALSE;
+    Object result;
+    try {
+      result = extFn.evaluate(resolveArgs());
+    } catch (XPathFunctionException e) {
+      if (invalidFunctionException(e)) {
+        unrecoverableError = true;
+        logger.error("Error executing XPathFunction", e);
+      } else {
+        if (logger.isDebugEnabled()) logger.debug("Error executing XPathFunction", e);
+      }
+      throw new QueryException("Error executing external function", e);
+    }
+    return (result.getClass().equals(URI.class)) ? new IRI((URI)result) : TypedLiteral.newLiteral(result);
   }
 
   /**
@@ -186,4 +228,41 @@
     if (!result.isLiteral() && !(result instanceof NumericExpression)) throw new QueryException("Type Error: Not valid to ask the numeric form of a: " + result.getClass().getSimpleName());
     return ((NumericExpression)result).getNumber();
   }
+
+  /**
+   * Look for a function in the registered XPathFunctionResolvers.
+   * @param iri The URI of the function to find.
+   * @return The requested XPathFunction, or <code>null</code> if not found.
+   */
+  private XPathFunction findFunction(IRI iri, int argCount) {
+    String label = iri.toString() + "/" + argCount;
+    XPathFunction result = fnCache.get(label);
+    if (result == null) {
+      QName fnName = iri.getQName();
+      if (fnName == null) return null;
+      for (XPathFunctionResolver resolver: FunctionResolverRegistry.getFunctionResolverRegistry()) {
+        try {
+          result = resolver.resolveFunction(fnName, argCount);
+        } catch (Exception e) {
+          // this resolver is unable to handle the given QName
+        }
+        if (result != null) break;
+      }
+      if (result != null) fnCache.put(label, result);
+    }
+    return result;
+  }
+
+  /**
+   * Test if an exception indicates that a method is unavailable.
+   * @param e An exception thrown when an external method is invoked.
+   * @return <code>true</code> if the exception indicates that the method can never be successful.
+   */
+  private static boolean invalidFunctionException(Exception e) {
+    Throwable t = e;
+    do {
+      if (t instanceof NoSuchMethodException) return true;
+    } while ((t = t.getCause()) != null);
+    return false;
+  }
 }




More information about the Mulgara-svn mailing list