/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ /* * ParameterTable.java * * Created on April 12, 2000 */ package com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import com.sun.jdo.api.persistence.support.JDOQueryException; import com.sun.jdo.api.persistence.support.JDOFatalInternalException; import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.Type; import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.StringType; import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.PrimitiveType; import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.WrapperClassType; import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.MathType; import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.DateType; import org.glassfish.persistence.common.I18NHelper; import com.sun.jdo.spi.persistence.support.sqlstore.ValueFetcher; import com.sun.jdo.spi.persistence.utility.JavaTypeHelper; import com.sun.jdo.spi.persistence.utility.ParameterInfo; /** * The query parameter table * * @author Michael Bouschen * @version 0.1 */ public class ParameterTable { /** Query parameter names */ List names = null; /** Query parameter types */ List types = null; /** Query Parameter values */ transient List values = null; /** null key */ private static final String NULL_ = "null"; //NOI18N /** true key */ private static final String TRUE_ = "true"; //NOI18N /** false key */ private static final String FALSE_ = "false"; //NOI18N /** other key */ private static final String OTHER_ = "other"; //NOI18N /** noparams key */ private static final String NOPARAMS_ = "noparams"; //NOI18N /** key parameter separator */ private static final char PARAMKEY_SEPARATOR = '/'; /** * Objects of this class represent the value for an unbound query parameter */ static class Unbound { } /** * Singleton representing the value for an unbound query parameter */ static final Unbound unbound = new Unbound(); /** I18N support */ protected final static ResourceBundle messages = I18NHelper.loadBundle(ParameterTable.class); /** * */ public ParameterTable() {} /** * Copy constructor. * @param other the ParameterTable to be copied */ public ParameterTable(ParameterTable other) { this.names = other.names; this.types = other.types; this.values = other.values; } /** * Adds a new query parameter with the specified type to the query * parameter table. */ public void add(String name, Type type) { names.add(name); types.add(type); } /** * Initializes the parameter declarations (names and types list). * Needs to be called prior to any add call. */ public void init() { this.names = new ArrayList(); this.types = new ArrayList(); } /** * Initializes the parameter values. This methods sets the values for all * declared parameters to unbound. */ public void initValueHandling() { values = new ArrayList(names.size()); final int size = names.size(); for (int i = 0; i < size; i++) { values.add(unbound); } } /** * Check actual query parameters specified as array and return the * ValueFetcher for the inputparameters. * @param actualParams */ public void setValues(Object[] actualParams) { if (actualParams != null) { for (int i = 0; i < actualParams.length; i++) { Object value = actualParams[i]; defineValueByIndex(i, value); } } } /** * Checks whether all parameters have an actual value. */ public void checkUnboundParams() { final int size = values.size(); for (int i = 0; i < size; i++) { if (values.get(i) == unbound) { throw new JDOQueryException( I18NHelper.getMessage(messages, "jqlc.parametertable.checkunboundparams.unboundparam", //NOI18N names.get(i))); } } } /** * Check actual query parameters specified as map and return the * ValueFetcher for the inputparameters. * @param actualParams */ public void setValues(Map actualParams) { if (actualParams != null) { for (Iterator i = actualParams.entrySet().iterator(); i.hasNext();) { Map.Entry actualParam = (Map.Entry)i.next(); String name = (String)actualParam.getKey(); Object value = actualParam.getValue(); defineValueByName(name, value); } } } /** * Returns the value of the parameter with the specified name. */ public Object getValueByName(String name) { int index = names.indexOf(name); if (index == -1) throw new JDOFatalInternalException(I18NHelper.getMessage( messages, "jqlc.parametertable.getvaluebyname.undefined", //NOI18N name)); return getValueByIndex(index); } /** * Returns the value of the parameter with the specified index. */ public Object getValueByIndex(int index) { if ((index < 0) || (index >= values.size())) throw new JDOFatalInternalException(I18NHelper.getMessage( messages, "jqlc.parametertable.getvaluebyindex.wrongindex", //NOI18N String.valueOf(index))); return values.get(index); } /** Returns the list of parameter values. */ public List getValues() { return values; } /** * Wraps the actual parameter array into a ValueFetcher instnace. * @return Instance of ValueFetcher */ public ValueFetcher getValueFetcher() { return new QueryValueFetcher(values.toArray(new Object[values.size()])); } /** * Calculates and returns the key for the RetrieveDesc cache based, * on the actual parameter values. * A <code>null</code> return means, the RetrieveDesc should not be * cached. * Note, this method needs to be in sync with method inline. */ public String getKeyForRetrieveDescCache() { StringBuffer key = new StringBuffer(); final int size = values.size(); for (int i = 0; i < size; i++) { // Do not cache RetrieveDesc if the parameter type is pc class // or java.lang.Object => return null if (isInlineType(types.get(i))) return null; Object item = values.get(i); if (item == null) { key.append(ParameterTable.NULL_); } else if (item instanceof Boolean) { if (((Boolean)item).booleanValue()) { key.append(ParameterTable.TRUE_); } else { key.append(ParameterTable.FALSE_); } } else { key.append(ParameterTable.OTHER_); } key.append(ParameterTable.PARAMKEY_SEPARATOR); } // If the key is 0 in length, the Query does not use any parameters. // But nevertheless we want cache the RD, thus we return a key for // no-parameter-queries if (key.length() == 0) { key.append(ParameterTable.NOPARAMS_); } return key.toString(); } /** * Returns true if the parameter with the specified index should be inlined * by the optimizer. * Note, this method needs to be in sync with method * getKeyForRetrieveDescCache. * @param paramName the parameter * @return true if the specified parameter should be inlined. */ public boolean inline(String paramName) { int index = names.indexOf(paramName); Object value = values.get(index); if (isInlineType(types.get(index))) return true; if (value == null) return true; if (value instanceof Boolean) return true; return false; } /** * Returns <code>true</code> if the specified parameter denotes a type * whose values should be inlined by the query optimizer if a query * parameter s is declared with such a type. */ private boolean isInlineType(Object type) { // Check for types that are supported by JDBC, such that the // parameter can be mapped to a JDBC parameter, these are: // - String // - primitive types (int, float, etc.) // - wrapper class types (Integer, Float, etc.) // - BigDecimal, BigInteger // - Date class types // All other types including pc classes, java.lang.Object, etc. // should be inlined. if ((type instanceof StringType) || (type instanceof PrimitiveType) || (type instanceof WrapperClassType) || (type instanceof MathType) || (type instanceof DateType)) return false; return true; } /** * Returns the parameter index for the specified parameter name. * @deprecated */ public Integer getIndexForParamName(String paramName) { return new Integer(names.indexOf(paramName)); } /** * Returns the parameter info for the specified parameter name. * @param paramName * @return corresponding parameterInfo */ public ParameterInfo getParameterInfoForParamName(String paramName) { return getParameterInfoForParamName(paramName, null); } /** * Returns the parameter info for the specified parameter name * and associated field. * If the associated field is not known, then null is used as * input parameter. * @param paramName * @param associatedField * @return corresponding parameterInfo */ public ParameterInfo getParameterInfoForParamName(String paramName, String associatedField) { int index = names.indexOf(paramName); Type type = (Type)types.get(index); return new ParameterInfo(index, type.getEnumType(), associatedField); } /** * */ private void defineValueByName(String name, Object value) { int index = names.indexOf(name); if (index == -1) throw new JDOQueryException( I18NHelper.getMessage(messages, "jqlc.parametertable.definevaluebyname.undefinedparam", name)); //NOI18N defineValueByIndex(index, value); } /** * */ private void defineValueByIndex(int index, Object value) { // index < 0 => implementation error if (index < 0) throw new JDOFatalInternalException(I18NHelper.getMessage( messages, "jqlc.parametertable.definevaluebyindex.wrongindex", //NOI18N String.valueOf(index))); // index > type.size => too many actual parameters if (index >= types.size()) throw new JDOQueryException( I18NHelper.getMessage(messages, "jqlc.parametertable.definevaluebyindex.wrongnumberofargs")); //NOI18N // check type compatibility of actual and formal parameter Class formalType = ((Type)types.get(index)).getJavaClass(); if (!isCompatibleValue(formalType, value)) { String actualTypeName = ((value==null) ? "<type of null>" : value.getClass().getName()); throw new JDOQueryException( I18NHelper.getMessage(messages, "jqlc.parametertable.definevaluebyindex.typemismatch", //NOI18N actualTypeName, formalType.getName())); } // everything is ok => set the actual parameters's value values.set(index, value); } /** * Checks whether the type of the specified value is compatible with the * specified formal type. * @param name the formal type. * @param the value to be checked * @return <code>true</code> if the type of the value is compatible with the * formal type; <code>false</code> otherwise. */ private boolean isCompatibleValue(Class formalType, Object value) { boolean isCompatible = true; // handle value == null if (value == null) { isCompatible = !formalType.isPrimitive(); } else { Class actualType = value.getClass(); if (formalType.isPrimitive()) formalType = JavaTypeHelper.getWrapperClass(formalType); isCompatible = formalType.isAssignableFrom(actualType); } return isCompatible; } }