1 /*****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20 package org.apache.james.util;
21
22 import org.apache.oro.text.perl.MalformedPerl5PatternException;
23 import org.apache.oro.text.perl.Perl5Util;
24 import org.w3c.dom.Attr;
25 import org.w3c.dom.Document;
26 import org.w3c.dom.Element;
27 import org.w3c.dom.NamedNodeMap;
28 import org.w3c.dom.NodeList;
29
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import java.io.File;
33 import java.sql.Connection;
34 import java.sql.SQLException;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.Map;
38
39
40 /***
41 * Provides a set of SQL String resources (eg SQL Strings)
42 * to use for a database connection.
43 * This class allows SQL strings to be customised to particular
44 * database products, by detecting product information from the
45 * jdbc DatabaseMetaData object.
46 *
47 */
48 public class SqlResources
49 {
50 /***
51 * A map of statement types to SQL statements
52 */
53 private Map m_sql = new HashMap();
54
55 /***
56 * A map of engine specific options
57 */
58 private Map m_dbOptions = new HashMap();
59
60 /***
61 * A set of all used String values
62 */
63 static private Map stringTable = java.util.Collections.synchronizedMap(new HashMap());
64
65 /***
66 * A Perl5 regexp matching helper class
67 */
68 private Perl5Util m_perl5Util = new Perl5Util();
69
70 /***
71 * Configures a DbResources object to provide SQL statements from a file.
72 *
73 * SQL statements returned may be specific to the particular type
74 * and version of the connected database, as well as the database driver.
75 *
76 * Parameters encoded as $(parameter} in the input file are
77 * replace by values from the parameters Map, if the named parameter exists.
78 * Parameter values may also be specified in the resourceSection element.
79 *
80 * @param sqlFile the input file containing the string definitions
81 * @param sqlDefsSection
82 * the xml element containing the strings to be used
83 * @param conn the Jdbc DatabaseMetaData, taken from a database connection
84 * @param configParameters a map of parameters (name-value string pairs) which are
85 * replaced where found in the input strings
86 */
87 public void init(File sqlFile, String sqlDefsSection,
88 Connection conn, Map configParameters)
89 throws Exception
90 {
91
92 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
93 DocumentBuilder builder = factory.newDocumentBuilder();
94 Document sqlDoc = builder.parse(sqlFile);
95
96
97
98 Element dbMatcherElement =
99 (Element)(sqlDoc.getElementsByTagName("dbMatchers").item(0));
100 String dbProduct = null;
101 if ( dbMatcherElement != null ) {
102 dbProduct = matchDbConnection(conn, dbMatcherElement);
103 m_perl5Util = null;
104 }
105
106
107 Element dbOptionsElement =
108 (Element)(sqlDoc.getElementsByTagName("dbOptions").item(0));
109 if ( dbOptionsElement != null ) {
110
111 populateDbOptions("", dbOptionsElement, m_dbOptions);
112
113 if ( dbProduct != null ) {
114 populateDbOptions(dbProduct, dbOptionsElement, m_dbOptions);
115 }
116 }
117
118
119
120 NodeList sections = sqlDoc.getElementsByTagName("sqlDefs");
121 int sectionsCount = sections.getLength();
122 Element sectionElement = null;
123 boolean found = false;
124 for (int i = 0; i < sectionsCount; i++ ) {
125 sectionElement = (Element)(sections.item(i));
126 String sectionName = sectionElement.getAttribute("name");
127 if ( sectionName != null && sectionName.equals(sqlDefsSection) ) {
128 found = true;
129 break;
130 }
131
132 }
133 if ( !found ) {
134 StringBuffer exceptionBuffer =
135 new StringBuffer(64)
136 .append("Error loading sql definition file. ")
137 .append("The element named \'")
138 .append(sqlDefsSection)
139 .append("\' does not exist.");
140 throw new RuntimeException(exceptionBuffer.toString());
141 }
142
143
144
145 Map parameters = new HashMap();
146
147 Element parametersElement =
148 (Element)(sectionElement.getElementsByTagName("parameters").item(0));
149 if ( parametersElement != null ) {
150 NamedNodeMap params = parametersElement.getAttributes();
151 int paramCount = params.getLength();
152 for (int i = 0; i < paramCount; i++ ) {
153 Attr param = (Attr)params.item(i);
154 String paramName = param.getName();
155 String paramValue = param.getValue();
156 parameters.put(paramName, paramValue);
157 }
158 }
159
160 parameters.putAll(configParameters);
161
162
163
164
165 Map defaultSqlStatements = new HashMap();
166 Map dbProductSqlStatements = new HashMap();
167
168
169
170 NodeList sqlDefs = sectionElement.getElementsByTagName("sql");
171 int sqlCount = sqlDefs.getLength();
172 for ( int i = 0; i < sqlCount; i++ ) {
173
174 Element sqlElement = (Element)(sqlDefs.item(i));
175 String sqlDb = sqlElement.getAttribute("db");
176 Map sqlMap;
177 if ( sqlDb.equals("")) {
178
179 sqlMap = defaultSqlStatements;
180 }
181 else if (sqlDb.equals(dbProduct) ) {
182
183 sqlMap = dbProductSqlStatements;
184 }
185 else {
186
187 continue;
188 }
189
190
191 String sqlKey = sqlElement.getAttribute("name");
192 if ( sqlKey == null ) {
193
194 continue;
195 }
196 String sqlString = sqlElement.getFirstChild().getNodeValue();
197
198
199 Iterator paramNames = parameters.keySet().iterator();
200 while ( paramNames.hasNext() ) {
201 String paramName = (String)paramNames.next();
202 String paramValue = (String)parameters.get(paramName);
203
204 StringBuffer replaceBuffer =
205 new StringBuffer(64)
206 .append("${")
207 .append(paramName)
208 .append("}");
209 sqlString = substituteSubString(sqlString, replaceBuffer.toString(), paramValue);
210 }
211
212
213 String shared = (String) stringTable.get(sqlString);
214
215 if (shared == null) {
216 stringTable.put(sqlString, sqlString);
217 } else {
218 sqlString = shared;
219 }
220
221
222 sqlMap.put(sqlKey, sqlString);
223 }
224
225
226 m_sql.putAll(defaultSqlStatements);
227 m_sql.putAll(dbProductSqlStatements);
228 }
229
230 /***
231 * Compares the DatabaseProductName value for a jdbc Connection
232 * against a set of regular expressions defined in XML.
233 * The first successful match defines the name of the database product
234 * connected to. This value is then used to choose the specific SQL
235 * expressions to use.
236 *
237 * @param conn the JDBC connection being tested
238 * @param dbMatchersElement the XML element containing the database type information
239 *
240 * @return the type of database to which James is connected
241 *
242 */
243 private String matchDbConnection(Connection conn,
244 Element dbMatchersElement)
245 throws MalformedPerl5PatternException, SQLException
246 {
247 String dbProductName = conn.getMetaData().getDatabaseProductName();
248
249 NodeList dbMatchers =
250 dbMatchersElement.getElementsByTagName("dbMatcher");
251 for ( int i = 0; i < dbMatchers.getLength(); i++ ) {
252
253 Element dbMatcher = (Element)dbMatchers.item(i);
254 String dbMatchName = dbMatcher.getAttribute("db");
255 StringBuffer dbProductPatternBuffer =
256 new StringBuffer(64)
257 .append("/")
258 .append(dbMatcher.getAttribute("databaseProductName"))
259 .append("/i");
260
261
262
263 if ( m_perl5Util.match(dbProductPatternBuffer.toString(), dbProductName) ) {
264 return dbMatchName;
265 }
266 }
267 return null;
268 }
269
270 /***
271 * Gets all the name/value pair db option couples related to the dbProduct,
272 * and put them into the dbOptionsMap.
273 *
274 * @param dbProduct the db product used
275 * @param dbOptionsElement the XML element containing the options
276 * @param dbOptionsMap the <CODE>Map</CODE> to populate
277 *
278 */
279 private void populateDbOptions(String dbProduct, Element dbOptionsElement, Map dbOptionsMap)
280 {
281 NodeList dbOptions =
282 dbOptionsElement.getElementsByTagName("dbOption");
283 for ( int i = 0; i < dbOptions.getLength(); i++ ) {
284
285 Element dbOption = (Element)dbOptions.item(i);
286
287
288 if (!dbProduct.equalsIgnoreCase(dbOption.getAttribute("db"))) {
289 continue;
290 }
291
292 dbOptionsMap.put(dbOption.getAttribute("name"), dbOption.getAttribute("value"));
293 }
294 }
295
296 /***
297 * Replace substrings of one string with another string and return altered string.
298 * @param input input string
299 * @param find the string to replace
300 * @param replace the string to replace with
301 * @return the substituted string
302 */
303 private String substituteSubString( String input,
304 String find,
305 String replace )
306 {
307 int find_length = find.length();
308 int replace_length = replace.length();
309
310 StringBuffer output = new StringBuffer(input);
311 int index = input.indexOf(find);
312 int outputOffset = 0;
313
314 while ( index > -1 ) {
315 output.replace(index + outputOffset, index + outputOffset + find_length, replace);
316 outputOffset = outputOffset + (replace_length - find_length);
317
318 index = input.indexOf(find, index + find_length);
319 }
320
321 String result = output.toString();
322 return result;
323 }
324
325 /***
326 * Returns a named SQL string for the specified connection,
327 * replacing parameters with the values set.
328 *
329 * @param name the name of the SQL resource required.
330 * @return the requested resource
331 */
332 public String getSqlString(String name)
333 {
334 return (String)m_sql.get(name);
335 }
336
337 /***
338 * Returns a named SQL string for the specified connection,
339 * replacing parameters with the values set.
340 *
341 * @param name the name of the SQL resource required.
342 * @param required true if the resource is required
343 * @return the requested resource
344 * @throws ConfigurationException
345 * if a required resource cannot be found.
346 */
347 public String getSqlString(String name, boolean required)
348 {
349 String sql = getSqlString(name);
350
351 if (sql == null && required) {
352 StringBuffer exceptionBuffer =
353 new StringBuffer(64)
354 .append("Required SQL resource: '")
355 .append(name)
356 .append("' was not found.");
357 throw new RuntimeException(exceptionBuffer.toString());
358 }
359 return sql;
360 }
361
362 /***
363 * Returns the dbOption string value set for the specified dbOption name.
364 *
365 * @param name the name of the dbOption required.
366 * @return the requested dbOption value
367 */
368 public String getDbOption(String name)
369 {
370 return (String)m_dbOptions.get(name);
371 }
372
373 }