// Copyright 2010 Google Inc. // // Licensed 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 com.google.enterprise.connector.util.database; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.enterprise.connector.spi.DatabaseResourceBundle; import net.jmatrix.eproperties.EProperties; import java.io.IOException; import java.net.URL; import java.util.List; import java.util.Properties; import java.util.logging.Logger; /** * Supplies SQL language syntax variations for the various database * implementations. This mirrors {@link java.util.PropertyResourceBundle} * in form and function, but is designed to supply SQL language translations * rather than spoken language translations. * <p> * {@code DatabasePropertyResourceBundles} are typically loaded using a * {@link DatabaseResourceBundleManager}. * <p> * Like {@code java.util.PropertyResourceBundle}, {@code DatabaseResourceBundle} * resources are specified in {@code Properties} files. Unlike * {@link java.util.PropertyResourceBundle}, {@code DatabaseResourceBundle} uses * the <a href="http://code.google.com/p/eproperties">EProperties</a> * enhanced Properties package, which extends the standard * {@code java.util.Properties} and syntax to include things like: * variable substitution, nesting, inclusion and lists. * For instance: * <ul> * <li>Variable substitution syntax that mirrors the syntax used in * shell scripts, ant, etc.: * <pre> * table.name = connector_instances * getvalue.query = "SELECT ${column.connector.name} FROM ${table.name} ..." * </pre> * Variables may be defined in the {@code DatabaseResourceBundle} in which they * are used, or any of the ancestor {@code DatabaseResourceBundles} (see below). * Variable substitution is done at the time of the call to * {@link #getString(String)} or {@link #getStringArray(String)}, so the * returned string(s) have all known substitutions resolved.</li> * <li>Lists that may be fetched as an array of Strings - great for potentially * multi-statement DDL. For instance, this table creation DDL resource * definition consists of three distinct SQL statements: * <pre> * create.table.ddl = ( "CREATE TABLE ...", "CREATE TRIGGER ...", "CREATE INDEX ..." ) * </pre> * which can be retrieved using {@link #getStringArray(String)}: * <pre> * String[] ddlStatements = bundle.getStringArray("create.table.ddl"); * </pre> * </li> * </ul> * * @since 2.8 */ public class DatabasePropertyResourceBundle implements DatabaseResourceBundle { private static final Logger LOGGER = Logger.getLogger(DatabaseResourceBundle.class.getName()); private final EProperties properties; private DatabasePropertyResourceBundle parent = null; /** * Creates a {@link DatabaseResourceBundle} backed by the properties * loaded from the {@code resourceUrl} and the given {@code parent}. * * @param resourceUrl the resource URL from which to read Properties * @param parent parent the DatabasePropertyResourceBundle to assign as parent * - may be {@code null} * @throws NullPointerException if {@code resourceUrl} is {@code null} * @throws IOException if there is an error reading the resource file */ public DatabasePropertyResourceBundle(URL resourceUrl, DatabasePropertyResourceBundle parent) throws IOException { Preconditions.checkNotNull(resourceUrl); if (parent == null) { properties = new EProperties(); } else { this.parent = parent; properties = new EProperties(parent.properties); } properties.load(resourceUrl); } /** * Wraps a {@code DatabasePropertyResouceBundle} around the supplied set * of {@link Properties}. * * @param properties a {@link Properties} instance * @throws NullPointerException if {@code properties} is {@code null} */ @VisibleForTesting DatabasePropertyResourceBundle(Properties properties) { Preconditions.checkNotNull(properties); if (properties instanceof EProperties) { this.properties = (EProperties) properties; } else { this.properties = new EProperties(); this.properties.addAll(properties); } } /** * Sets the parent {@code DatabasePropertyResourceBundle} of this * {@code DatabasePropertyResourceBundle}. * * @param parent a DatabasePropertyResourceBundle */ @VisibleForTesting void setParent(DatabasePropertyResourceBundle parent) { this.parent = parent; // Hook up the parent's Properties to be the parent of these // Properties. This lets the Properties implementation traverse // the ancestry, looking for matches. It also allows EProperties // substitutions to perform as expected. properties.setParent((parent == null) ? null : parent.properties, null); } /** Returns the parent {@code DatabasePropertyResourceBundle}. */ @VisibleForTesting DatabasePropertyResourceBundle getParent() { return parent; } /** * Gets a string for the given key from this resource bundle or one of * its parents. * * @param key the key for the desired string * @return the String for the given key, or {@code null} if no * resource was found for this key * @throws NullPointerException if {@code key} is {@code null} */ @Override public String getString(String key) { Preconditions.checkNotNull(key); String value = properties.findProperty(key); LOGGER.finest("Get database resource: " + key + " = " + value); return value; } private static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * Gets an array of Strings for the given {@code key} from this resource * bundle or one of its parents. * * @param key the key for the desired string array * @return a String[] for the given key, or {@code null} if no * resource was found for this key * @throws NullPointerException if {@code key} is {@code null} */ @Override @SuppressWarnings("unchecked") public String[] getStringArray(String key) { Preconditions.checkNotNull(key); Object value = properties.findValue(key); if (value != null) { if (value instanceof String) { LOGGER.finest("Get database resource: " + key + " = " + value); return new String[] { (String) value }; } if (value instanceof List) { String[] values = ((List<String>) value).toArray(EMPTY_STRING_ARRAY); LOGGER.finest("Get database resource: " + key + " = " + values); return values; } } return null; } }