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     */
017    package org.apache.commons.dbutils;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.util.HashMap;
022    import java.util.Map;
023    import java.util.Properties;
024    
025    /**
026     * <code>QueryLoader</code> is a registry for sets of queries so
027     * that multiple copies of the same queries aren't loaded into memory.
028     * This implementation loads properties files filled with query name to
029     * SQL mappings.  This class is thread safe.
030     */
031    public class QueryLoader {
032    
033        /**
034         * The Singleton instance of this class.
035         */
036        private static final QueryLoader instance = new QueryLoader();
037    
038        /**
039         * Return an instance of this class.
040         * @return The Singleton instance.
041         */
042        public static QueryLoader instance() {
043            return instance;
044        }
045    
046        /**
047         * Maps query set names to Maps of their queries.
048         */
049        private final Map<String, Map<String, String>> queries = new HashMap<String, Map<String, String>>();
050    
051        /**
052         * QueryLoader constructor.
053         */
054        protected QueryLoader() {
055            super();
056        }
057    
058        /**
059         * Loads a Map of query names to SQL values.  The Maps are cached so a
060         * subsequent request to load queries from the same path will return
061         * the cached Map.
062         *
063         * @param path The path that the ClassLoader will use to find the file.
064         * This is <strong>not</strong> a file system path.  If you had a jarred
065         * Queries.properties file in the com.yourcorp.app.jdbc package you would
066         * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method.
067         * @throws IOException if a file access error occurs
068         * @throws IllegalArgumentException if the ClassLoader can't find a file at
069         * the given path.
070         * @return Map of query names to SQL values
071         */
072        public synchronized Map<String, String> load(String path) throws IOException {
073    
074            Map<String, String> queryMap = this.queries.get(path);
075    
076            if (queryMap == null) {
077                queryMap = this.loadQueries(path);
078                this.queries.put(path, queryMap);
079            }
080    
081            return queryMap;
082        }
083    
084        /**
085         * Loads a set of named queries into a Map object.  This implementation
086         * reads a properties file at the given path.
087         * @param path The path that the ClassLoader will use to find the file.
088         * @throws IOException if a file access error occurs
089         * @throws IllegalArgumentException if the ClassLoader can't find a file at
090         * the given path.
091         * @since DbUtils 1.1
092         * @return Map of query names to SQL values
093         */
094        protected Map<String, String> loadQueries(String path) throws IOException {
095            // Findbugs flags getClass().getResource as a bad practice; maybe we should change the API?
096            InputStream in = getClass().getResourceAsStream(path);
097    
098            if (in == null) {
099                throw new IllegalArgumentException(path + " not found.");
100            }
101    
102            Properties props = new Properties();
103            try {
104                props.load(in);
105            } finally {
106                in.close();
107            }
108    
109            // Copy to HashMap for better performance
110    
111            @SuppressWarnings({ "rawtypes", "unchecked" }) // load() always creates <String,String> entries
112            HashMap<String, String> hashMap = new HashMap(props);
113            return hashMap;
114        }
115    
116        /**
117         * Removes the queries for the given path from the cache.
118         * @param path The path that the queries were loaded from.
119         */
120        public synchronized void unload(String path) {
121            this.queries.remove(path);
122        }
123    
124    }