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 }