1 /*
2 * Copyright (C) The Apache Software Foundation. All rights reserved.
3 *
4 * This software is published under the terms of the Apache Software License
5 * version 1.1, a copy of which has been included with this distribution in
6 * the LICENSE file.
7 */
8 package org.apache.james.util;
9
10 import org.apache.oro.text.perl.MalformedPerl5PatternException;
11 import org.apache.oro.text.perl.Perl5Util;
12 import org.w3c.dom.*;
13
14 import javax.xml.parsers.DocumentBuilder;
15 import javax.xml.parsers.DocumentBuilderFactory;
16 import java.io.File;
17 import java.sql.Connection;
18 import java.sql.SQLException;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.Map;
22
23
24 /***
25 * Provides a set of SQL String resources (eg SQL Strings)
26 * to use for a database connection.
27 * This class allows SQL strings to be customised to particular
28 * database products, by detecting product information from the
29 * jdbc DatabaseMetaData object.
30 *
31 * @author Darrell DeBoer <dd@bigdaz.com>
32 */
33 public class SqlResources
34 {
35 /***
36 * A map of statement types to SQL statements
37 */
38 private Map m_sql = new HashMap();
39
40 /***
41 * A Perl5 regexp matching helper class
42 */
43 private Perl5Util m_perl5Util = new Perl5Util();
44
45 /***
46 * Configures a DbResources object to provide SQL statements from a file.
47 *
48 * SQL statements returned may be specific to the particular type
49 * and version of the connected database, as well as the database driver.
50 *
51 * Parameters encoded as $(parameter} in the input file are
52 * replace by values from the parameters Map, if the named parameter exists.
53 * Parameter values may also be specified in the resourceSection element.
54 *
55 * @param sqlFile the input file containing the string definitions
56 * @param sqlDefsSection
57 * the xml element containing the strings to be used
58 * @param conn the Jdbc DatabaseMetaData, taken from a database connection
59 * @param configParameters a map of parameters (name-value string pairs) which are
60 * replaced where found in the input strings
61 */
62 public void init(File sqlFile, String sqlDefsSection,
63 Connection conn, Map configParameters)
64 throws Exception
65 {
66 // Parse the sqlFile as an XML document.
67 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
68 DocumentBuilder builder = factory.newDocumentBuilder();
69 Document sqlDoc = builder.parse(sqlFile);
70
71 // First process the database matcher, to determine the
72 // sql statements to use.
73 Element dbMatcherElement =
74 (Element)(sqlDoc.getElementsByTagName("dbMatchers").item(0));
75 String dbProduct = null;
76 if ( dbMatcherElement != null ) {
77 dbProduct = matchDbConnection(conn, dbMatcherElement);
78 }
79
80 // Now get the section defining sql for the repository required.
81 NodeList sections = sqlDoc.getElementsByTagName("sqlDefs");
82 int sectionsCount = sections.getLength();
83 Element sectionElement = null;
84 for (int i = 0; i < sectionsCount; i++ ) {
85 sectionElement = (Element)(sections.item(i));
86 String sectionName = sectionElement.getAttribute("name");
87 if ( sectionName != null && sectionName.equals(sqlDefsSection) ) {
88 break;
89 }
90
91 }
92 if ( sectionElement == null ) {
93 StringBuffer exceptionBuffer =
94 new StringBuffer(64)
95 .append("Error loading sql definition file. ")
96 .append("The element named \'")
97 .append(sqlDefsSection)
98 .append("\' does not exist.");
99 throw new RuntimeException(exceptionBuffer.toString());
100 }
101
102 // Get parameters defined within the file as defaults,
103 // and use supplied parameters as overrides.
104 Map parameters = new HashMap();
105 // First read from the <params> element, if it exists.
106 Element parametersElement =
107 (Element)(sectionElement.getElementsByTagName("parameters").item(0));
108 if ( parametersElement != null ) {
109 NamedNodeMap params = parametersElement.getAttributes();
110 int paramCount = params.getLength();
111 for (int i = 0; i < paramCount; i++ ) {
112 Attr param = (Attr)params.item(i);
113 String paramName = param.getName();
114 String paramValue = param.getValue();
115 parameters.put(paramName, paramValue);
116 }
117 }
118 // Then copy in the parameters supplied with the call.
119 parameters.putAll(configParameters);
120
121 // 2 maps - one for storing default statements,
122 // the other for statements with a "db" attribute matching this
123 // connection.
124 Map defaultSqlStatements = new HashMap();
125 Map dbProductSqlStatements = new HashMap();
126
127 // Process each sql statement, replacing string parameters,
128 // and adding to the appropriate map..
129 NodeList sqlDefs = sectionElement.getElementsByTagName("sql");
130 int sqlCount = sqlDefs.getLength();
131 for ( int i = 0; i < sqlCount; i++ ) {
132 // See if this needs to be processed (is default or product specific)
133 Element sqlElement = (Element)(sqlDefs.item(i));
134 String sqlDb = sqlElement.getAttribute("db");
135 Map sqlMap;
136 if ( sqlDb.equals("")) {
137 // default
138 sqlMap = defaultSqlStatements;
139 }
140 else if (sqlDb.equals(dbProduct) ) {
141 // Specific to this product
142 sqlMap = dbProductSqlStatements;
143 }
144 else {
145 // for a different product
146 continue;
147 }
148
149 // Get the key and value for this SQL statement.
150 String sqlKey = sqlElement.getAttribute("name");
151 if ( sqlKey == null ) {
152 // ignore statements without a "name" attribute.
153 continue;
154 }
155 String sqlString = sqlElement.getFirstChild().getNodeValue();
156
157 // Do parameter replacements for this sql string.
158 Iterator paramNames = parameters.keySet().iterator();
159 while ( paramNames.hasNext() ) {
160 String paramName = (String)paramNames.next();
161 String paramValue = (String)parameters.get(paramName);
162
163 StringBuffer replaceBuffer =
164 new StringBuffer(64)
165 .append("${")
166 .append(paramName)
167 .append("}");
168 sqlString = substituteSubString(sqlString, replaceBuffer.toString(), paramValue);
169 }
170
171 // Add to the sqlMap - either the "default" or the "product" map
172 sqlMap.put(sqlKey, sqlString);
173 }
174
175 // Copy in default strings, then overwrite product-specific ones.
176 m_sql.putAll(defaultSqlStatements);
177 m_sql.putAll(dbProductSqlStatements);
178 }
179
180 /***
181 * Compares the DatabaseProductName value for a jdbc Connection
182 * against a set of regular expressions defined in XML.
183 * The first successful match defines the name of the database product
184 * connected to. This value is then used to choose the specific SQL
185 * expressions to use.
186 *
187 * @param conn the JDBC connection being tested
188 * @param dbMatchersElement the XML element containing the database type information
189 *
190 * @return the type of database to which James is connected
191 *
192 */
193 private String matchDbConnection(Connection conn,
194 Element dbMatchersElement)
195 throws MalformedPerl5PatternException, SQLException
196 {
197 String dbProductName = conn.getMetaData().getDatabaseProductName();
198
199 NodeList dbMatchers =
200 dbMatchersElement.getElementsByTagName("dbMatcher");
201 for ( int i = 0; i < dbMatchers.getLength(); i++ ) {
202 // Get the values for this matcher element.
203 Element dbMatcher = (Element)dbMatchers.item(i);
204 String dbMatchName = dbMatcher.getAttribute("db");
205 StringBuffer dbProductPatternBuffer =
206 new StringBuffer(64)
207 .append("/")
208 .append(dbMatcher.getAttribute("databaseProductName"))
209 .append("/i");
210
211 // If the connection databaseProcuctName matches the pattern,
212 // use the match name from this matcher.
213 if ( m_perl5Util.match(dbProductPatternBuffer.toString(), dbProductName) ) {
214 return dbMatchName;
215 }
216 }
217 return null;
218 }
219
220 /***
221 * Replace substrings of one string with another string and return altered string.
222 * @param input input string
223 * @param find the string to replace
224 * @param replace the string to replace with
225 * @return the substituted string
226 */
227 private String substituteSubString( String input,
228 String find,
229 String replace )
230 {
231 int find_length = find.length();
232 int replace_length = replace.length();
233
234 StringBuffer output = new StringBuffer(input);
235 int index = input.indexOf(find);
236 int outputOffset = 0;
237
238 while ( index > -1 ) {
239 output.replace(index + outputOffset, index + outputOffset + find_length, replace);
240 outputOffset = outputOffset + (replace_length - find_length);
241
242 index = input.indexOf(find, index + find_length);
243 }
244
245 String result = output.toString();
246 return result;
247 }
248
249 /***
250 * Returns a named SQL string for the specified connection,
251 * replacing parameters with the values set.
252 *
253 * @param name the name of the SQL resource required.
254 * @return the requested resource
255 */
256 public String getSqlString(String name)
257 {
258 return (String)m_sql.get(name);
259 }
260
261 /***
262 * Returns a named SQL string for the specified connection,
263 * replacing parameters with the values set.
264 *
265 * @param name the name of the SQL resource required.
266 * @param required true if the resource is required
267 * @return the requested resource
268 * @throws ConfigurationException
269 * if a required resource cannot be found.
270 */
271 public String getSqlString(String name, boolean required)
272 {
273 String sql = getSqlString(name);
274
275 if ( sql == null ) {
276 StringBuffer exceptionBuffer =
277 new StringBuffer(64)
278 .append("Required SQL resource: '")
279 .append(name)
280 .append("' was not found.");
281 throw new RuntimeException(exceptionBuffer.toString());
282 }
283 return sql;
284 }
285 }
This page was automatically generated by Maven