001: /*
002: * Copyright 2006-2007 The Scriptella Project Team.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package scriptella.spi;
018:
019: import scriptella.configuration.ConfigurationException;
020: import scriptella.configuration.ConnectionEl;
021: import scriptella.util.ExceptionUtils;
022: import scriptella.util.IOUtils;
023: import scriptella.util.StringUtils;
024:
025: import java.io.File;
026: import java.net.MalformedURLException;
027: import java.net.URI;
028: import java.net.URL;
029: import java.nio.charset.Charset;
030: import java.util.Collections;
031: import java.util.HashMap;
032: import java.util.Map;
033:
034: /**
035: * Represents connection parameters.
036: *
037: * @author Fyodor Kupolov
038: * @version 1.0
039: */
040: public class ConnectionParameters {
041: private Map<String, ?> properties;
042: private String url;
043: private String user;
044: private String password;
045: private String schema;
046: private String catalog;
047: private DriverContext context;
048:
049: /**
050: * For testing purposes.
051: */
052: protected ConnectionParameters() {
053: }
054:
055: /**
056: * Creates connection parameters based on <connection> element..
057: *
058: * @param conf connection declaration element.
059: * @param context driver context.
060: */
061: public ConnectionParameters(ConnectionEl conf, DriverContext context) {
062: this .properties = conf.getProperties();
063: this .url = conf.getUrl();
064: this .user = conf.getUser();
065: this .password = conf.getPassword();
066: this .schema = conf.getSchema();
067: this .catalog = conf.getCatalog();
068:
069: this .context = context;
070: }
071:
072: /**
073: * This method returns properties for connection specified inside <connection> element
074: *
075: * @return properties map.
076: */
077: public Map<String, ?> getProperties() {
078: return properties;
079: }
080:
081: /**
082: * Convenience method which returns property by name.
083: *
084: * @param name property name
085: * @return property value
086: * @see #getProperties()
087: */
088: public Object getProperty(String name) {
089: return properties.get(name);
090: }
091:
092: /**
093: * Returns string value of the property.
094: *
095: * @param name property name.
096: * @return property value.
097: */
098: public String getStringProperty(String name) {
099: Object v = properties.get(name);
100: return v == null ? null : v.toString();
101: }
102:
103: /**
104: * Returns the value of integer property.
105: *
106: * @param name name of the property.
107: * @param defaultValue default value if property is absent.
108: * @return property value.
109: * @throws scriptella.configuration.ConfigurationException
110: * if parsing failed.
111: * @see #getNumberProperty(String,Number)
112: */
113: public Integer getIntegerProperty(String name, int defaultValue)
114: throws ConfigurationException {
115: return getNumberProperty(name, defaultValue).intValue();
116: }
117:
118: public Integer getIntegerProperty(String name)
119: throws ConfigurationException {
120: Number res = getNumberProperty(name, null);
121: return res == null ? null : res.intValue();
122: }
123:
124: /**
125: * Returns numeric value of the property.
126: * <p>Accepts decimal, hexadecimal, and octal numbers if property is String.
127: *
128: * @param name property name.
129: * @param defaultValue default value to use when property omitted.
130: * @return numeric property value.
131: * @throws scriptella.configuration.ConfigurationException
132: * if parsing failed.
133: * @see Long#decode(String)
134: */
135: public Number getNumberProperty(String name, Number defaultValue)
136: throws ConfigurationException {
137: Object v = properties.get(name);
138: if (v == null) {
139: return defaultValue;
140: }
141: if (v instanceof Number) {
142: return ((Number) v);
143: }
144: String s = v.toString().trim();
145: if (s.length() == 0) {
146: return defaultValue;
147: }
148:
149: //For now we do not support doubles etc.
150: try {
151: return Long.decode(s);
152: } catch (NumberFormatException e) {
153: throw new ConfigurationException(name
154: + " property must be integer.");
155: }
156: }
157:
158: /**
159: * Parses property value as boolean flag. Default value is <code>false</code>.
160: *
161: * @param name property name.
162: * @return boolean property value.
163: * @throws scriptella.configuration.ConfigurationException
164: * if property has unrecognized value.
165: * @see #getBooleanProperty(String,boolean)
166: */
167: public boolean getBooleanProperty(String name)
168: throws ConfigurationException {
169: return getBooleanProperty(name, false);
170: }
171:
172: /**
173: * Parses property value as a boolean flag.
174: *
175: * @param name property name.
176: * @param defaultValue default value to use if connection has no such property.
177: * @return boolean property value.
178: * @throws ConfigurationException if property has unrecognized value.
179: */
180: public boolean getBooleanProperty(String name, boolean defaultValue)
181: throws ConfigurationException {
182: Object a = getProperty(name);
183: if (a == null) {
184: return defaultValue;
185: }
186: if (a instanceof Boolean) {
187: return (Boolean) a;
188: }
189: if (a instanceof Number) {
190: return ((Number) a).intValue() > 0;
191: }
192: String s = a.toString().trim();
193:
194: if ("true".equalsIgnoreCase(s) || "1".equalsIgnoreCase(s)
195: || "on".equalsIgnoreCase(s)
196: || "yes".equalsIgnoreCase(s)) {
197: return true;
198: }
199:
200: if ("false".equalsIgnoreCase(s) || "0".equalsIgnoreCase(s)
201: || "off".equalsIgnoreCase(s)
202: || "no".equalsIgnoreCase(s)) {
203: return false;
204: }
205: throw new ConfigurationException(
206: "Unrecognized boolean property value " + a);
207: }
208:
209: /**
210: * Parses property value as a charset encoding name.
211: *
212: * @param name property name.
213: * @return value of the property or null if connection has no such property.
214: * @throws ConfigurationException if charset name is unsupported.
215: */
216: public String getCharsetProperty(String name)
217: throws ConfigurationException {
218: Object cs = getProperty(name);
219: if (cs == null) {
220: return null;
221: }
222: if (cs instanceof Charset) {
223: return ((Charset) cs).name();
224: }
225: String enc = cs.toString().trim();
226: if (!Charset.isSupported(enc)) {
227: throw new ConfigurationException("Specified encoding "
228: + enc
229: + " is not supported. Supported encodings are "
230: + Charset.availableCharsets().keySet());
231: }
232: return enc;
233: }
234:
235: /**
236: * Parses property value as URL parameter.
237: * <p>Relative URIs are resolved using a script file location as a base.
238: *
239: * @param name property name
240: * @return value of the property or null if connection has no such property.
241: * @throws ConfigurationException if URL is malformed.
242: */
243: public URL getUrlProperty(String name)
244: throws ConfigurationException {
245: Object u = getProperty(name);
246: if (u == null) {
247: return null;
248: }
249: if (u instanceof URL) {
250: return (URL) u;
251: }
252: try {
253: if (u instanceof URI) {
254: URI uri = (URI) u;
255: return uri.toURL();
256: }
257: if (u instanceof File) {
258: File f = (File) u;
259: return IOUtils.toUrl(f);
260: }
261: } catch (MalformedURLException e) {
262: ExceptionUtils.ignoreThrowable(e);
263: //If malformed URL try to use the toString resolving
264: }
265:
266: try {
267: String uri = u.toString().trim();
268: return getContext().resolve(uri);
269: } catch (MalformedURLException e) {
270: throw new ConfigurationException("Specified URL " + u
271: + " is malformed");
272: }
273:
274: }
275:
276: /**
277: * Returns connection URL.
278: * <p>URL format is arbitrary and specified by Service Provider.
279: *
280: * @return connection URL
281: */
282: public String getUrl() {
283: return url;
284: }
285:
286: /**
287: * Convenience method which parses URL string, extracts a query string and returns a map of URL query parameters.
288: * <p>The query is assumed to have the following syntax: ?param=value¶m2=value¶m3
289: * <p>If parameter is declared twice, only the last value is returned in a map.
290: * If parameter has only a key, but no value part than the returned map would contain key=key mapping, e.g.
291: * jdbc:url?readonly produces a map of 1 entry <code>readonly=readonly</code>.
292: * <p>The drivers may use this method to support overriding connection parameters in an URL string.
293: *
294: * @return map of parsed parameters.
295: */
296: public Map<String, String> getUrlQueryMap() {
297: if (StringUtils.isEmpty(url)) {
298: return Collections.emptyMap();
299: }
300: int lastInd = url.indexOf('?') + 1;
301: if (lastInd > 0) {
302: Map<String, String> map = new HashMap<String, String>();
303: final int urlLength = url.length();
304: do {
305: int i = url.indexOf('&', lastInd);
306: if (i < 0) {
307: i = urlLength;
308: }
309: String keyValue = url.substring(lastInd, i).trim();
310: if (!StringUtils.isEmpty(keyValue)) {
311: int eqPos = keyValue.indexOf('=');
312: if (eqPos > 0) { //If '=' present, split key and value
313: String key = keyValue.substring(0, eqPos)
314: .trim();
315: String value = keyValue.substring(eqPos + 1)
316: .trim();
317: map.put(key, value);
318: } else { //otherwise use key=value=keyValue
319: map.put(keyValue, keyValue);
320: }
321: }
322: lastInd = i + 1;
323: } while (lastInd < urlLength);
324: return map;
325: } else {
326: return Collections.emptyMap();
327: }
328:
329: }
330:
331: /**
332: * Returns the url property resolved relative to a script location.
333: *
334: * @throws ConfigurationException if connection URL is malformed or null.
335: * @return resolved URL.
336: */
337: public URL getResolvedUrl() throws ConfigurationException {
338: if (url == null) {
339: throw new ConfigurationException(
340: "URL connection attribute is requred");
341: }
342: try {
343: return getContext().resolve(url);
344: } catch (MalformedURLException e) {
345: throw new ConfigurationException(
346: "Specified connection URL " + url + " is malformed");
347: }
348: }
349:
350: /**
351: * Optional user name for connection.
352: *
353: * @return user name
354: */
355: public String getUser() {
356: return user;
357: }
358:
359: /**
360: * Optional user password for connection.
361: *
362: * @return user password
363: */
364: public String getPassword() {
365: return password;
366: }
367:
368: /**
369: * @return optional schema name
370: */
371: public String getSchema() {
372: return schema;
373: }
374:
375: /**
376: * @return optional catalog name
377: */
378: public String getCatalog() {
379: return catalog;
380: }
381:
382: /**
383: * Get a drivers context.
384: *
385: * @return script context.
386: */
387: public DriverContext getContext() {
388: return context;
389: }
390:
391: public String toString() {
392: return "ConnectionParameters{" + "properties=" + properties
393: + ", url='" + url + '\'' + ", user='" + user + '\''
394: + ", password='" + password + '\'' + ", schema='"
395: + schema + '\'' + ", catalog='" + catalog + '\'' + '}';
396: }
397: }
|