    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.dbutils;
    018
    019import java.io.IOException;
    020import java.io.InputStream;
    021import java.util.HashMap;
    022import java.util.Map;
    023import java.util.Properties;
    024import java.util.regex.Pattern;
    025
    026/**
    027 * {@code QueryLoader} is a registry for sets of queries so
    028 * that multiple copies of the same queries aren't loaded into memory.
    029 * This implementation loads properties files filled with query name to
    030 * SQL mappings.  This class is thread safe.
    031 */
    032public class QueryLoader {
    033
    034    /**
    035     * The Singleton INSTANCE of this class.
    036     */
    037    private static final QueryLoader INSTANCE = new QueryLoader();
    038
    039    /**
    040     * Matches .xml file extensions.
    041     */
    042    private static final Pattern dotXml = Pattern.compile(".+\\.[xX][mM][lL]");
    043
    044    /**
    045     * Return an INSTANCE of this class.
    046     * @return The Singleton INSTANCE.
    047     */
    048    public static QueryLoader instance() {
    049        return INSTANCE;
    050    }
    051
    052    /**
    053     * Maps query set names to Maps of their queries.
    054     */
    055    private final Map<String, Map<String, String>> queries = new HashMap<>();
    056
    057    /**
    058     * QueryLoader constructor.
    059     */
    060    protected QueryLoader() {
    061    }
    062
    063    /**
    064     * Loads a Map of query names to SQL values.  The Maps are cached so a
    065     * subsequent request to load queries from the same path will return
    066     * the cached Map.  The properties file to load can be in either
    067     * line-oriented or XML format.  XML formatted properties files must use a
    068     * {@code .xml} file extension.
    069     *
    070     * @param path The path that the ClassLoader will use to find the file.
    071     * This is <strong>not</strong> a file system path.  If you had a jarred
    072     * Queries.properties file in the com.yourcorp.app.jdbc package you would
    073     * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method.
    074     * @throws IOException if a file access error occurs
    075     * @throws IllegalArgumentException if the ClassLoader can't find a file at
    076     * the given path.
    077     * @throws java.util.InvalidPropertiesFormatException if the XML properties file is
    078     * invalid
    079     * @return Map of query names to SQL values
    080     * @see java.util.Properties
    081     */
    082    public synchronized Map<String, String> load(final String path) throws IOException {
    083
    084        Map<String, String> queryMap = this.queries.get(path);
    085
    086        if (queryMap == null) {
    087            queryMap = this.loadQueries(path);
    088            this.queries.put(path, queryMap);
    089        }
    090
    091        return queryMap;
    092    }
    093
    094    /**
    095     * Loads a set of named queries into a Map object.  This implementation
    096     * reads a properties file at the given path.  The properties file can be
    097     * in either line-oriented or XML format.  XML formatted properties files
    098     * must use a {@code .xml} file extension.
    099
    100     * @param path The path that the ClassLoader will use to find the file.
    101     * @throws IOException if a file access error occurs
    102     * @throws IllegalArgumentException if the ClassLoader can't find a file at
    103     * the given path.
    104     * @throws java.util.InvalidPropertiesFormatException if the XML properties file is
    105     * invalid
    106     * @since 1.1
    107     * @return Map of query names to SQL values
    108     * @see java.util.Properties
    109     */
    110    protected Map<String, String> loadQueries(final String path) throws IOException {
    111        // Findbugs flags getClass().getResource as a bad practice; maybe we should change the API?
    112        final Properties props;
    113        try (InputStream in = getClass().getResourceAsStream(path)) {
    114
    115            if (in == null) {
    116                throw new IllegalArgumentException(path + " not found.");
    117            }
    118            props = new Properties();
    119            if (dotXml.matcher(path).matches()) {
    120                props.loadFromXML(in);
    121            } else {
    122                props.load(in);
    123            }
    124        }
    125
    126        // Copy to HashMap for better performance
    127
    128        @SuppressWarnings({"rawtypes", "unchecked" }) // load() always creates <String,String> entries
    129        final HashMap<String, String> hashMap = new HashMap(props);
    130        return hashMap;
    131    }
    132
    133    /**
    134     * Removes the queries for the given path from the cache.
    135     * @param path The path that the queries were loaded from.
    136     */
    137    public synchronized void unload(final String path) {
    138        this.queries.remove(path);
    139    }
    140
    141}