/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.sparql.function.user;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.expr.Expr ;
import org.apache.jena.sparql.expr.ExprTransformer ;
import org.apache.jena.sparql.function.Function ;
import org.apache.jena.sparql.function.FunctionFactory ;
import org.apache.jena.sparql.function.FunctionRegistry ;
import org.apache.jena.sparql.lang.sparql_11.ParseException ;
import org.apache.jena.sparql.lang.sparql_11.SPARQLParser11 ;
import org.apache.jena.sparql.sse.builders.ExprBuildException ;
/**
* A function factory for managing user defined functions aka function macros.
* <p>
* User defined functions provide a simple mechanism for a user to inject custom
* functions into SPARQL processing without the need to write any code. These
* functions essentially act as macros/aliases for another SPARQL expression and
* serve as a means to aid users in simplifying their SPARQL queries.
* </p>
* <p>
* For example we can define a <strong>square</strong> function like so:
* </p>
*
* <pre>
* List<Var> args = new ArrayList<Var>(Var.alloc("x"));
* UserDefinedFunctionFactory.getFactory().add("http://example/square", "?x * ?x", args);
* </pre>
* <p>
* We can then use this in queries like so:
* </p>
*
* <pre>
* SELECT (<http://example/square>(3) AS ?ThreeSquared) { }
* </pre>
* <p>
* Internally the call to the <strong>square</strong> function is translated
* into its equivalent SPARQL expression and executed in that form.
* </p>
* <p>
* User defined functions may rely on each other but this has some risks,
* therefore the default behaviour is to not preserve these dependencies but
* rather to expand the function definitions to give the resulting expression
* associated with a function. Please see {@link #getPreserveDependencies()} for
* more information on this.
* </p>
*/
public class UserDefinedFunctionFactory implements FunctionFactory {
private static UserDefinedFunctionFactory factory = new UserDefinedFunctionFactory();
/**
* Gets the static instance of the factory
*
* @return Function Factory
*/
public static UserDefinedFunctionFactory getFactory() {
return factory;
}
private Map<String, UserDefinedFunctionDefinition> definitions = new HashMap<>();
private boolean preserveDependencies = false;
/**
* Private constructor prevents instantiation
*/
private UserDefinedFunctionFactory() {
}
/**
* Gets whether user defined functions may preserve dependencies on each
* other (default false)
* <p>
* When this is disabled (as it is by default) function definitions are
* fully expanded at registration time. So if you add a function that
* references an existing user defined function it will be expanded to
* include the resulting expression rather than left with a reference to
* another function. This protects the user from depending on other
* functions whose definitions are later removed or changed.
* </p>
* <p>
* However it may sometimes be desirable to have dependencies preserved
* in which case this option may be disabled with the corresponding
* {@link #setPreserveDependencies(boolean)} setter
* </p>
*
* @return Whether explicit dependencies are allowed
*/
public boolean getPreserveDependencies() {
return this.preserveDependencies;
}
/**
* Sets whether user functions may explicitly depend on each other, see
* {@link #getPreserveDependencies()} for explanation of this behavior
*
* @param allow
* Whether to preserve dependencies
*/
public void setPreserveDependencies(boolean allow) {
this.preserveDependencies = allow;
}
/**
* Creates a function for the given URI
*
* @throws ExprBuildException
* Thrown if the given URI is not a known function
*/
@Override
public Function create(String uri) {
UserDefinedFunctionDefinition def = this.definitions.get(uri);
if (def == null)
throw new ExprBuildException("Function <" + uri + "> not known by this function factory");
return def.newFunctionInstance();
}
/**
* Adds a function
*
* @param uri
* URI
* @param e
* Expression
* @param args
* Arguments
*/
public void add(String uri, Expr e, List<Var> args) {
if (!preserveDependencies) {
// If not allowing dependencies expand expression fully
e = ExprTransformer.transform(new ExprTransformExpand(this.definitions), e);
}
UserDefinedFunctionDefinition def = new UserDefinedFunctionDefinition(uri, e, args);
this.definitions.put(uri, def);
FunctionRegistry.get().put(uri, this);
}
/**
* Adds a function
* <p>
* This method will build the expression to use based on the expression
* string given, strings must match the SPARQL expression syntax e.g.
* </p>
*
* <pre>
* (?x * ?y) + 5
* </pre>
*
* @param uri
* URI
* @param expr
* Expression String (in SPARQL syntax)
* @param args
* Arguments
* @throws ParseException
* Thrown if the expression string is not valid syntax
*/
public void add(String uri, String expr, List<Var> args) throws ParseException {
Expr e = new SPARQLParser11(new StringReader(expr)).Expression();
if (!preserveDependencies) {
// If not allowing dependencies expand expression fully
e = ExprTransformer.transform(new ExprTransformExpand(this.definitions), e);
}
UserDefinedFunctionDefinition def = new UserDefinedFunctionDefinition(uri, e, args);
this.definitions.put(uri, def);
FunctionRegistry.get().put(uri, this);
}
/**
* Removes a function definition
*
* @param uri
* URI
* @throws NoSuchElementException
* Thrown if a function with the given URI does not exist
*/
public void remove(String uri) {
if (!this.definitions.containsKey(uri))
throw new NoSuchElementException("No function definition is associated with the URI <" + uri + ">");
this.definitions.remove(uri);
FunctionRegistry.get().remove(uri);
}
/**
* Gets the definition of the function (if registered)
*
* @param uri
* URI
* @return Function Definition if registered, null otherwise
*/
public UserDefinedFunctionDefinition get(String uri) {
if (!this.definitions.containsKey(uri))
return null;
return this.definitions.get(uri);
}
/**
* Gets whether a function with the given URI has been registered
*
* @param uri
* URI
* @return True if registered, false otherwise
*/
public boolean isRegistered(String uri) {
return this.definitions.containsKey(uri);
}
/**
* Clears all function definitions
*/
public void clear() {
for (String uri : this.definitions.keySet()) {
FunctionRegistry.get().remove(uri);
}
this.definitions.clear();
}
}