/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.function;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.core.runtime.IStatus;
import org.teiid.UserDefinedAggregate;
import org.teiid.core.CoreConstants;
import org.teiid.core.types.DataTypeManagerService;
import org.teiid.core.types.DataTypeManagerService.DefaultDataTypes;
import org.teiid.core.util.ReflectionHelper;
import org.teiid.designer.annotation.Since;
import org.teiid.designer.runtime.version.spi.ITeiidServerVersion;
import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version;
import org.teiid.metadata.AbstractMetadataRecord;
import org.teiid.metadata.FunctionMethod;
import org.teiid.metadata.FunctionMethod.PushDown;
import org.teiid.metadata.FunctionParameter;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Procedure;
import org.teiid.metadata.Schema;
import org.teiid.query.function.metadata.FunctionCategoryConstants;
import org.teiid.query.parser.AbstractTeiidParser;
import org.teiid.query.util.CommandContext;
import org.teiid.runtime.client.Messages;
import org.teiid.runtime.client.TeiidClientException;
import org.teiid.runtime.client.TeiidRuntimePlugin;
/**
* Data structure used to store function signature information. There are multiple uses
* of this signature information so there are multiple data structures within the FunctionTree
* for handling each. One type of information is the function metadata required by users of
* this class for data driving GUIs or function selection. The other type of information is that
* needed to quickly find and/or invoke the functions at execution time. In general all methods
* are concerned with function metadata EXCEPT {@link #getFunction} which is used to find a function
* for execution.
*/
public class FunctionTree {
private final ITeiidServerVersion teiidVersion;
// Constant used to look up the special descriptor key in a node map
private static final Integer DESCRIPTOR_KEY = -1;
private Map<String, Set<FunctionMethod>> categories = new TreeMap<String, Set<FunctionMethod>>();
private Map<String, List<FunctionMethod>> functionsByName = new TreeMap<String, List<FunctionMethod>>(String.CASE_INSENSITIVE_ORDER);
@Since(Version.TEIID_8_9)
private Map<String, FunctionMethod> functionsByUuid = new TreeMap<String, FunctionMethod>(String.CASE_INSENSITIVE_ORDER);
@Since(Version.TEIID_8_9)
private String schemaName;
private Set<FunctionMethod> allFunctions = new HashSet<FunctionMethod>();
@Since(Version.TEIID_8_9)
private int idCount;
/**
* Function lookup and invocation use: Function name (uppercase) to Map (recursive tree)
*/
private Map<String, Map<Object, Object>> treeRoot = new TreeMap<String, Map<Object, Object>>(String.CASE_INSENSITIVE_ORDER);
private boolean validateClass;
private DataTypeManagerService dataTypeManager;
/**
* Construct a new tree with the given source of function metadata.
* @param teiidVersion
* @param name name
* @param source The metadata source
*/
public FunctionTree(ITeiidServerVersion teiidVersion, String name, FunctionMetadataSource source) {
this(teiidVersion, name, source, false);
}
/**
* Construct a new tree with the given source of function metadata.
* @param teiidVersion teiid version
* @param name name
* @param source The metadata source
* @param validateClass validating class
*/
public FunctionTree(ITeiidServerVersion teiidVersion, String name, FunctionMetadataSource source, boolean validateClass) {
this.teiidVersion = teiidVersion;
this.schemaName = name;
// Load data structures
this.validateClass = validateClass;
boolean system = CoreConstants.SYSTEM_MODEL.equalsIgnoreCase(name) || CoreConstants.SYSTEM_ADMIN_MODEL.equalsIgnoreCase(name);
Collection<FunctionMethod> functions = source.getFunctionMethods(teiidVersion);
for (FunctionMethod method : functions) {
if (!containsIndistinguishableFunction(method)){
// Add to tree
addFunction(name, source, method, system);
} else if (!CoreConstants.SYSTEM_MODEL.equalsIgnoreCase(name)) {
TeiidRuntimePlugin.log(IStatus.WARNING, null, Messages.gs(Messages.TEIID.TEIID30011, method));
}
}
}
public DataTypeManagerService getDataTypeManager() {
if (dataTypeManager == null)
dataTypeManager = DataTypeManagerService.getInstance(teiidVersion);
return dataTypeManager;
}
@Since(Version.TEIID_8_9)
public String getSchemaName() {
return schemaName;
}
@Since(Version.TEIID_8_9)
public Map<String, FunctionMethod> getFunctionsByUuid() {
return functionsByUuid;
}
// ---------------------- FUNCTION SELECTION USE METHODS ----------------------
/*
* Per defect 4612 -
* Because of the fix for defect 4264, it is possible in the modeler to
* define two functions with different implementations, but having the
* same name (using the "alias") and the same parameter types, making the
* two FunctionMethod objects indistinguishable by their equals method.
* This method will check if any indistinguishable functions are already
* present in this FunctionTree. If so, it will be logged and any
* newer indistinguishable functions will just not be added.
*/
private boolean containsIndistinguishableFunction(FunctionMethod method){
return allFunctions.contains(method);
}
/**
* Get collection of category names.
* @return Category names
*/
Collection<String> getCategories() {
return categories.keySet();
}
Set<FunctionMethod> getFunctionsInCategory(String name) {
Set<FunctionMethod> names = categories.get(name);
if (names == null) {
return Collections.emptySet();
}
return names;
}
/**
* Find all function methods with the given name and arg length
* @param name Function name, case insensitive
* @param args Number of arguments
* @return Corresponding form or null if not found
*/
List<FunctionMethod> findFunctionMethods(String name, int args) {
final List<FunctionMethod> allMatches = new ArrayList<FunctionMethod>();
List<FunctionMethod> methods = functionsByName.get(name);
if(methods == null || methods.size() == 0) {
return allMatches;
}
for (FunctionMethod functionMethod : methods) {
if(functionMethod.getInputParameterCount() == args || functionMethod.isVarArgs() && args >= functionMethod.getInputParameterCount() - 1) {
allMatches.add(functionMethod);
}
}
return allMatches;
}
// ---------------------- FUNCTION INVOCATION USE METHODS ----------------------
/**
* Store the method for function resolution and invocation.
* @param source The function metadata source, which knows how to obtain the invocation class
* @param method The function metadata for a particular method signature
*/
public FunctionDescriptor addFunction(String schema, FunctionMetadataSource source, FunctionMethod method, boolean system) {
String categoryKey = method.getCategory();
if (categoryKey == null) {
method.setCategory(FunctionCategoryConstants.MISCELLANEOUS);
categoryKey = FunctionCategoryConstants.MISCELLANEOUS;
}
setUuid(method);
// Look up function map (create if necessary)
Set<FunctionMethod> functions = categories.get(categoryKey);
if (functions == null) {
functions = new HashSet<FunctionMethod>();
categories.put(categoryKey, functions);
}
// Get method name
String methodName = schema + AbstractMetadataRecord.NAME_DELIM_CHAR + method.getName();
// Get input types for path
List<FunctionParameter> inputParams = method.getInputParameters();
Class<?>[] types = null;
if(inputParams != null) {
types = new Class<?>[inputParams.size()];
for(int i=0; i<inputParams.size(); i++) {
String typeName = inputParams.get(i).getType();
Class<?> clazz = getDataTypeManager().getDataTypeClass(typeName);
types[i] = clazz;
setUuid(inputParams.get(i));
}
} else {
types = new Class<?>[0];
}
setUuid(method.getOutputParameter());
FunctionDescriptor descriptor = createFunctionDescriptor(source, method, types, system);
descriptor.setSchema(schema);
// Store this path in the function tree
// Look up function in function map
functions.add(method);
functionsByUuid.put(method.getUUID(), method);
while(true) {
// Add method to list by function name
List<FunctionMethod> knownMethods = functionsByName.get(methodName);
if(knownMethods == null) {
knownMethods = new ArrayList<FunctionMethod>();
functionsByName.put(methodName, knownMethods);
}
knownMethods.add(method);
Map<Object, Object> node = treeRoot.get(methodName);
if (node == null) {
node = new HashMap<Object, Object>(2);
treeRoot.put(methodName, node);
}
for(int pathIndex = 0; pathIndex < types.length; pathIndex++) {
Class<?> pathPart = types[pathIndex];
Map<Object, Object> children = (Map<Object, Object>) node.get(pathPart);
if(children == null) {
children = new HashMap<Object, Object>(2);
node.put(pathPart, children);
}
if (method.isVarArgs() && pathIndex == types.length - 1) {
node.put(DESCRIPTOR_KEY, descriptor);
Map<Object, Object> alternate = new HashMap<Object, Object>(2);
alternate.put(DESCRIPTOR_KEY, descriptor);
DefaultDataTypes dataType = getDataTypeManager().getDataType(pathPart);
node.put(dataType.getTypeArrayClass(), alternate);
}
node = children;
}
if (method.isVarArgs()) {
node.put(types[types.length - 1], node);
}
// Store the leaf descriptor in the tree
node.put(DESCRIPTOR_KEY, descriptor);
int index = methodName.indexOf(AbstractMetadataRecord.NAME_DELIM_CHAR);
if (index == -1) {
break;
}
methodName = methodName.substring(index+1);
}
allFunctions.add(method);
return descriptor;
}
/**
* Adapted from {@link MetadataFactory#setUUID}
* @param method
*/
private void setUuid(AbstractMetadataRecord method) {
if (!method.isUUIDSet()) {
int lsb = 0;
if (method.getParent() != null) {
lsb = method.getParent().getUUID().hashCode();
} else {
lsb = CoreConstants.SYSTEM_MODEL.hashCode();
}
lsb = 31*lsb + method.getName().hashCode();
String uuid = "tsid:" + MetadataFactory.hex(lsb, 16) + "-" + MetadataFactory.hex(idCount++, 8); //$NON-NLS-1$ //$NON-NLS-2$
method.setUUID(uuid);
}
}
private FunctionDescriptor createFunctionDescriptor(FunctionMetadataSource source, FunctionMethod method, Class<?>[] types, boolean system) {
try {
// Get return type
FunctionParameter outputParam = method.getOutputParameter();
Class<?> outputType = null;
if (outputParam != null) {
outputType = getDataTypeManager().getDataTypeClass(outputParam.getType());
}
List<Class<?>> inputTypes = new ArrayList<Class<?>>(Arrays.asList(types));
boolean hasWrappedArg = false;
if (!system) {
for (int i = 0; i < types.length; i++) {
if (types[i] == DataTypeManagerService.DefaultDataTypes.VARBINARY.getTypeClass()) {
hasWrappedArg = true;
inputTypes.set(i, byte[].class);
}
}
}
if (method.isVarArgs()) {
Class<?> klazz = inputTypes.get(inputTypes.size() - 1);
DefaultDataTypes dataType = getDataTypeManager().getDataType(klazz);
inputTypes.set(inputTypes.size() - 1, dataType.getTypeArrayClass());
}
Method invocationMethod = method.getMethod();
boolean requiresContext = false;
// Defect 20007 - Ignore the invocation method if pushdown is not required.
if (validateClass
&& (method.getPushdown() == PushDown.CAN_PUSHDOWN || method.getPushdown() == PushDown.CANNOT_PUSHDOWN)) {
if (invocationMethod == null) {
if (method.getInvocationClass() == null || method.getInvocationMethod() == null) {
throw new Exception(Messages.gs(Messages.TEIID.TEIID31123, method.getName()));
}
try {
Class<?> methodClass = source.getInvocationClass(method.getInvocationClass());
ReflectionHelper helper = new ReflectionHelper(methodClass);
try {
invocationMethod = helper.findBestMethodWithSignature(method.getInvocationMethod(), inputTypes);
} catch (NoSuchMethodException e) {
inputTypes.add(0, CommandContext.class);
invocationMethod = helper.findBestMethodWithSignature(method.getInvocationMethod(), inputTypes);
requiresContext = true;
}
} catch (ClassNotFoundException e) {
throw new TeiidClientException(e, Messages.gs(Messages.TEIID.TEIID30387,
method.getName(),
method.getInvocationClass()));
} catch (NoSuchMethodException e) {
throw new TeiidClientException(e, Messages.gs(Messages.TEIID.TEIID30388,
method,
method.getInvocationClass(),
method.getInvocationMethod()));
} catch (Exception e) {
throw new TeiidClientException(e, Messages.gs(Messages.TEIID.TEIID30389,
method,
method.getInvocationClass(),
method.getInvocationMethod()));
}
} else {
requiresContext = (invocationMethod.getParameterTypes().length > 0 && org.teiid.CommandContext.class.isAssignableFrom(invocationMethod.getParameterTypes()[0]));
}
if (invocationMethod != null) {
// Check return type is non void
Class<?> methodReturn = invocationMethod.getReturnType();
if (method.getAggregateAttributes() == null && methodReturn.equals(Void.TYPE)) {
throw new Exception(Messages.gs(Messages.TEIID.TEIID30390, method.getName(), invocationMethod));
}
// Check that method is public
int modifiers = invocationMethod.getModifiers();
if (!Modifier.isPublic(modifiers)) {
throw new Exception(Messages.gs(Messages.TEIID.TEIID30391, method.getName(), invocationMethod));
}
// Check that method is static
if (!Modifier.isStatic(modifiers)) {
if (method.getAggregateAttributes() == null) {
throw new Exception(Messages.gs(Messages.TEIID.TEIID30392, method.getName(), invocationMethod));
}
} else if (method.getAggregateAttributes() != null) {
throw new Exception(Messages.gs(Messages.TEIID.TEIID30600, method.getName(), invocationMethod));
}
if (method.getAggregateAttributes() != null
&& !(UserDefinedAggregate.class.isAssignableFrom(invocationMethod.getDeclaringClass()))) {
throw new Exception(Messages.gs(Messages.TEIID.TEIID30601,
method.getName(),
method.getInvocationClass(),
UserDefinedAggregate.class.getName()));
}
method.setMethod(invocationMethod);
}
}
FunctionDescriptor result = new FunctionDescriptor(teiidVersion, method, types, outputType,
invocationMethod, requiresContext, source.getClassLoader());
boolean validateClassResult = teiidVersion.isGreaterThanOrEqualTo(Version.TEIID_8_12_4) ? validateClass : true;
if (validateClassResult && method.getAggregateAttributes() != null
&& (method.getPushdown() == PushDown.CAN_PUSHDOWN || method.getPushdown() == PushDown.CANNOT_PUSHDOWN)) {
result.newInstance();
}
result.setHasWrappedArgs(hasWrappedArg);
return result;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Look up a function descriptor by signature in the tree. If none is
* found, null is returned.
* @param name Name of the function, case is not important
* @param argTypes Types of each argument in the function
* @return Descriptor which can be used to invoke the function
*/
FunctionDescriptor getFunction(String name, Class<?>[] argTypes) {
// Walk path in tree
Map<Object, Object> node = treeRoot.get(name);
if (node == null) {
return null;
}
for(int i=0; i<argTypes.length; i++) {
Map<Object, Object> nextNode = (Map<Object, Object>)node.get(argTypes[i]);
if (nextNode == null) {
if (argTypes[i].isArray()) {
//array types are not yet considered in the function typing logic
nextNode = (Map<Object, Object>) node.get(DataTypeManagerService.DefaultDataTypes.OBJECT.getTypeClass());
}
if (nextNode == null) {
return null;
}
}
node = nextNode;
}
// Look for key at the end
if(node.containsKey(DESCRIPTOR_KEY)) {
// This is the end - return descriptor
return (FunctionDescriptor) node.get(DESCRIPTOR_KEY);
}
// No descriptor at this location in tree
return null;
}
public static FunctionTree getFunctionProcedures(ITeiidServerVersion teiidVersion, Schema schema) {
UDFSource dummySource = new UDFSource(Collections.EMPTY_LIST, FunctionTree.class.getClassLoader());
FunctionTree ft = null;
for (Procedure p : schema.getProcedures().values()) {
if (p.isFunction() && p.getQueryPlan() != null) {
if (ft == null) {
ft = new FunctionTree(teiidVersion, schema.getName(), dummySource, false);
}
FunctionMethod fm = AbstractTeiidParser.createFunctionMethod(teiidVersion, p);
FunctionDescriptor fd = ft.addFunction(schema.getName(), dummySource, fm, false);
fd.setProcedure(p);
}
}
return ft;
}
}