Package org.teiid.query.function

Source Code of org.teiid.query.function.FunctionTree

/*
* 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.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.teiid.core.CoreConstants;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.util.ReflectionHelper;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.AbstractMetadataRecord;
import org.teiid.metadata.FunctionMethod;
import org.teiid.metadata.FunctionParameter;
import org.teiid.metadata.FunctionMethod.PushDown;
import org.teiid.query.QueryPlugin;
import org.teiid.query.function.metadata.FunctionCategoryConstants;
import org.teiid.query.util.CommandContext;


/**
* 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 {

    // Constant used to look up the special descriptor key in a node map
    private static final Integer DESCRIPTOR_KEY = new Integer(-1);

    private Map<String, Set<String>> categories = new HashMap<String, Set<String>>();

    private Map<String, List<FunctionMethod>> functionsByName = new HashMap<String, List<FunctionMethod>>();
   
    private Set<FunctionMethod> allFunctions = new HashSet<FunctionMethod>();

  /**
   * Function lookup and invocation use: Function name (uppercase) to Map (recursive tree)
   */
    private Map treeRoot = new HashMap();
    private boolean validateClass;
   
    /**
     * Construct a new tree with the given source of function metadata.
     * @param source The metadata source
     */
    public FunctionTree(String name, FunctionMetadataSource source) {
      this(name, source, false);
    }
   
    /**
     * Construct a new tree with the given source of function metadata.
     * @param source The metadata source
     */
    public FunctionTree(String name, FunctionMetadataSource source, boolean validateClass) {
        // Load data structures
      this.validateClass = validateClass;

        Collection<FunctionMethod> functions = source.getFunctionMethods();
      for (FunctionMethod method : functions) {
      if (!containsIndistinguishableFunction(method)){
                // Add to tree
                addFunction(name, source, method);
      } else if (!CoreConstants.SYSTEM_MODEL.equalsIgnoreCase(name)) {
                LogManager.logWarning(LogConstants.CTX_FUNCTION_TREE, QueryPlugin.Util.getString("ERR.015.001.0046", new Object[]{method})); //$NON-NLS-1$
      }
        }
    }

  // ---------------------- 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();
    }

    /**
     * Get collection of function forms in a category
     * @param category Category to get (case-insensitive)
     * @return Collection of {@link FunctionForm}s
     */
    Collection<FunctionForm> getFunctionForms(String category) {
        Set<FunctionForm> functionForms = new HashSet<FunctionForm>();

        Set<String> functions = categories.get(category.toUpperCase());
        if(functions != null) {
          for (String functionName : functions) {
            for (FunctionMethod functionMethod : this.functionsByName.get(functionName)) {
                    functionForms.add(new FunctionForm(functionMethod));
                }
            }
        }

        return functionForms;
    }

    /**
     * Find function form based on function name and # of arguments.
     * @param name Function name, case insensitive
     * @param args Number of arguments
     * @return Corresponding form or null if not found
     */
    FunctionForm findFunctionForm(String name, int args) {
      List<FunctionMethod> results = findFunctionMethods(name, args);
      if (results.size() > 0) {
        return new FunctionForm(results.get(0));
      }
      return null;
    }
   
    /**
     * 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.toUpperCase());
        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) {
      String categoryKey = method.getCategory();
      if (categoryKey == null) {
        method.setCategory(FunctionCategoryConstants.MISCELLANEOUS);
        categoryKey = FunctionCategoryConstants.MISCELLANEOUS;
      }
      categoryKey = categoryKey.toUpperCase();
       
        // Look up function map (create if necessary)
        Set<String> functions = categories.get(categoryKey);
        if (functions == null) {
            functions = new HashSet<String>();
            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();
        List<Class<?>> inputTypes = new LinkedList<Class<?>>();
        if(inputParams != null) {
            for(int i=0; i<inputParams.size(); i++) {
                String typeName = inputParams.get(i).getType();
                inputTypes.add(DataTypeManager.getDataTypeClass(typeName));
            }
        }
        Class<?>[] types = inputTypes.toArray(new Class[inputTypes.size()]);

        if (method.isVarArgs()) {
          inputTypes.set(inputTypes.size() - 1, Array.newInstance(inputTypes.get(inputTypes.size() - 1), 0).getClass());
        }

        FunctionDescriptor descriptor = createFunctionDescriptor(source, method, inputTypes, types);
        descriptor.setSchema(schema);
        // Store this path in the function tree
       
        int index = -1;
        while(true) {
            String nameKey = methodName.toUpperCase();

          // Look up function in function map
          functions.add(nameKey);
 
          // Add method to list by function name
          List<FunctionMethod> knownMethods = functionsByName.get(nameKey);
          if(knownMethods == null) {
              knownMethods = new ArrayList<FunctionMethod>();
              functionsByName.put(nameKey, knownMethods);
          }
          knownMethods.add(method);

          Map node = treeRoot;
          Object[] path = buildPath(methodName, types);
          for(int pathIndex = 0; pathIndex < path.length; pathIndex++) {
              Object pathPart = path[pathIndex];
              Map children = (Map) node.get(pathPart);
              if(children == null) {
                  children = new HashMap();
                  node.put(pathPart, children);
              }
              if (method.isVarArgs() && pathIndex == path.length - 1) {
              node.put(DESCRIPTOR_KEY, descriptor);
              }
              node = children;
          }
 
          if (method.isVarArgs()) {
            node.put(types[types.length - 1], node);
          }
          // Store the leaf descriptor in the tree
          node.put(DESCRIPTOR_KEY, descriptor);
         
          index = methodName.indexOf(AbstractMetadataRecord.NAME_DELIM_CHAR, index+1);
          if (index == -1) {
            break;
          }
          methodName = methodName.substring(index+1);
        }
       
        allFunctions.add(method);
        return descriptor;
    }

  private FunctionDescriptor createFunctionDescriptor(
      FunctionMetadataSource source, FunctionMethod method,
      List<Class<?>> inputTypes, Class<?>[] types) {
    // Get return type
        FunctionParameter outputParam = method.getOutputParameter();
        Class<?> outputType = null;
        if(outputParam != null) {
            outputType = DataTypeManager.getDataTypeClass(outputParam.getType());
        }

        Method invocationMethod = null;
        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)) {
            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 TeiidRuntimeException(e, "ERR.015.001.0047", QueryPlugin.Util.getString("FunctionTree.no_class", method.getName(), method.getInvocationClass())); //$NON-NLS-1$ //$NON-NLS-2$
            } catch (NoSuchMethodException e) {
              throw new TeiidRuntimeException(e, "ERR.015.001.0047", QueryPlugin.Util.getString("FunctionTree.no_method", method, method.getInvocationClass(), method.getInvocationMethod())); //$NON-NLS-1$ //$NON-NLS-2$
            } catch (Exception e) {               
                throw new TeiidRuntimeException(e, "ERR.015.001.0047", QueryPlugin.Util.getString("ERR.015.001.0047", method, method.getInvocationClass(), method.getInvocationMethod())); //$NON-NLS-1$ //$NON-NLS-2$
            }
            if (invocationMethod != null) {
              // Check return type is non void
            Class<?> methodReturn = invocationMethod.getReturnType();
            if(methodReturn.equals(Void.TYPE)) {
              throw new TeiidRuntimeException("ERR.015.001.0047", QueryPlugin.Util.getString("FunctionTree.not_void", method.getName(), invocationMethod)); //$NON-NLS-1$ //$NON-NLS-2$
            }

            // Check that method is public
            int modifiers = invocationMethod.getModifiers();
            if(! Modifier.isPublic(modifiers)) {
              throw new TeiidRuntimeException("ERR.015.001.0047", QueryPlugin.Util.getString("FunctionTree.not_public", method.getName(), invocationMethod)); //$NON-NLS-1$ //$NON-NLS-2$
            }

            // Check that method is static
            if(! Modifier.isStatic(modifiers)) {
              throw new TeiidRuntimeException("ERR.015.001.0047", QueryPlugin.Util.getString("FunctionTree.not_static", method.getName(), invocationMethod)); //$NON-NLS-1$ //$NON-NLS-2$
            }
            }
        } else {
            inputTypes.add(0, CommandContext.class);
        }

        return new FunctionDescriptor(method, types, outputType, invocationMethod, requiresContext);
  }
   
    /**
     * 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) {
        // Build search path
        Object[] path = buildPath(name, argTypes);

        // Walk path in tree
        Map node = treeRoot;
        for(int i=0; i<path.length; i++) {
          node = (Map)node.get(path[i]);
          if (node == null) {
            return null;
            }
        }

        // 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;
    }

    /**
     * Build the path in the function storage tree.  The path for a function consists
     * of it's name (uppercased) and each of the argument classes.
     * @param name Name of function
     * @param argTypes Types of each arguments
     * @return Path in function storage tree
     */
    private Object[] buildPath(String name, Class<?>[] argTypes) {
        Object[] path = new Object[argTypes.length + 1];
        path[0] = name.toUpperCase();
        System.arraycopy(argTypes, 0, path, 1, argTypes.length);
        return path;
    }
}
TOP

Related Classes of org.teiid.query.function.FunctionTree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.