View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbutils;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.Properties;
24  import java.util.regex.Pattern;
25  
26  /**
27   * {@code QueryLoader} is a registry for sets of queries so
28   * that multiple copies of the same queries aren't loaded into memory.
29   * This implementation loads properties files filled with query name to
30   * SQL mappings.  This class is thread safe.
31   */
32  public class QueryLoader {
33  
34      /**
35       * The Singleton INSTANCE of this class.
36       */
37      private static final QueryLoader INSTANCE = new QueryLoader();
38  
39      /**
40       * Matches .xml file extensions.
41       */
42      private static final Pattern dotXml = Pattern.compile(".+\\.[xX][mM][lL]");
43  
44      /**
45       * Return an INSTANCE of this class.
46       * @return The Singleton INSTANCE.
47       */
48      public static QueryLoader instance() {
49          return INSTANCE;
50      }
51  
52      /**
53       * Maps query set names to Maps of their queries.
54       */
55      private final Map<String, Map<String, String>> queries = new HashMap<>();
56  
57      /**
58       * QueryLoader constructor.
59       */
60      protected QueryLoader() {
61      }
62  
63      /**
64       * Loads a Map of query names to SQL values.  The Maps are cached so a
65       * subsequent request to load queries from the same path will return
66       * the cached Map.  The properties file to load can be in either
67       * line-oriented or XML format.  XML formatted properties files must use a
68       * {@code .xml} file extension.
69       *
70       * @param path The path that the ClassLoader will use to find the file.
71       * This is <strong>not</strong> a file system path.  If you had a jarred
72       * Queries.properties file in the com.yourcorp.app.jdbc package you would
73       * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method.
74       * @throws IOException if a file access error occurs
75       * @throws IllegalArgumentException if the ClassLoader can't find a file at
76       * the given path.
77       * @throws java.util.InvalidPropertiesFormatException if the XML properties file is
78       * invalid
79       * @return Map of query names to SQL values
80       * @see java.util.Properties
81       */
82      public synchronized Map<String, String> load(final String path) throws IOException {
83  
84          Map<String, String> queryMap = this.queries.get(path);
85  
86          if (queryMap == null) {
87              queryMap = this.loadQueries(path);
88              this.queries.put(path, queryMap);
89          }
90  
91          return queryMap;
92      }
93  
94      /**
95       * Loads a set of named queries into a Map object.  This implementation
96       * reads a properties file at the given path.  The properties file can be
97       * in either line-oriented or XML format.  XML formatted properties files
98       * must use a {@code .xml} file extension.
99  
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 }