001package org.javasimon.jdbc4;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.util.Properties;
006import java.util.StringTokenizer;
007import java.util.regex.Matcher;
008import java.util.regex.Pattern;
009
010/**
011 * JDBC configuration contains Simon JDBC connection URL, real JDBC connection URL and Simon properties.
012 * <p/>
013 * It parses given url and than provides getters for driver's properties if provided or default values.
014 * Prior to Simon 3.4, this class was known as org.javasimon.jdbc4.Driver.Url and only used in {@link Driver}.
015 * It was externalized and is now used in both {@code org.javasimon.jdbc4.Driver}
016 * and {@link org.javasimon.jdbcx4.SimonDataSource} and other Simon DataSources.
017 *
018 * @author Radovan Sninsky
019 * @author gquintana
020 * @since 3.4
021 */
022public class SimonConnectionConfiguration {
023        /**
024         * Default hierarchy prefix for Simon JDBC driver. All Simons created by Simon JDBC
025         * driver without explicitly specified prefix are started with default prefix.
026         */
027        public static final String DEFAULT_PREFIX = "org.javasimon.jdbc";
028
029        /**
030         * Prefix used in JDBC connection URLs.
031         */
032        public static final String URL_PREFIX = "jdbc:simon";
033
034        /**
035         * Regex used to parse JDBC connection URL.
036         */
037        private static final Pattern URL_PATTERN = Pattern.compile("jdbc:(simon:)?([\\w]*):.*");
038
039        /**
040         * Name for the property holding the real driver class value.
041         */
042        public static final String REAL_DRIVER = "simon_real_drv";
043        /**
044         * Name for the driver property holding the hierarchy prefix given to JDBC Simons.
045         */
046        public static final String PREFIX = "simon_prefix";
047
048        private static final Properties PROPERTIES = initProperties();
049
050        /**
051         * JDBC connection URL with Simon prefix.
052         */
053        private final String simonUrl;
054
055        /**
056         * JDBC connection URL without Simon prefix.
057         */
058        private final String realUrl;
059
060        /**
061         * Driver Id.
062         */
063        private final String driverId;
064
065        /**
066         * Real Driver class name.
067         */
068        private final String realDriver;
069
070        /**
071         * Simon hierarchy prefix.
072         */
073        private final String prefix;
074
075        /**
076         * Loads {@code driver.properties} file.
077         */
078        private static Properties initProperties() {
079                InputStream stream = null;
080                try {
081                        // TODO: limited to known drivers, better find driver later based on JDBC connection URL without "simon" word
082                        Properties properties = new Properties();
083                        stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("org/javasimon/jdbc4/drivers.properties");
084                        if (stream != null) {
085                                properties.load(stream);
086                        }
087                        return properties;
088                } catch (IOException e) {
089                        throw new IllegalStateException(e);
090                } finally {
091                        if (stream != null) {
092                                try {
093                                        stream.close();
094                                } catch (IOException e) {
095                                        // Doesn't matter
096                                }
097                        }
098                }
099        }
100
101        /**
102         * Class constructor, parses given connection URL and recognizes driver's properties.
103         *
104         * @param url given JDBC connection URL
105         */
106        public SimonConnectionConfiguration(String url) {
107                Matcher m = URL_PATTERN.matcher(url);
108                if (!m.matches()) {
109                        throw new IllegalArgumentException(url + " is not a valid JDBC connection URL");
110                }
111                driverId = m.group(2);
112                if (m.group(1) == null) {
113                        // java:oracle:
114                        simonUrl = url.replaceFirst("jdbc", URL_PREFIX);
115                        realUrl = url;
116                        realDriver = getProperty(driverId, "driver");
117                        prefix = DEFAULT_PREFIX;
118                } else {
119                        //java:simon:oracle
120                        simonUrl = url;
121                        StringTokenizer st = new StringTokenizer(url, ";");
122                        String lRealDriver = getProperty(driverId, "driver");
123                        String lPrefix = DEFAULT_PREFIX;
124                        StringBuilder realUrlBuilder = new StringBuilder();
125                        while (st.hasMoreTokens()) {
126                                String tokenPairStr = st.nextToken().trim();
127
128                                if (tokenPairStr.startsWith(URL_PREFIX)) {
129                                        realUrlBuilder.append(tokenPairStr.replaceFirst(URL_PREFIX, "jdbc"));
130                                } else {
131                                        String[] tokenPair = tokenPairStr.split("=", 2);
132                                        String token = tokenPair[0];
133                                        String tokenValue = tokenPair.length == 2 ? tokenPair[1].trim() : null;
134                                        if (token.equalsIgnoreCase(REAL_DRIVER)) {
135                                                lRealDriver = tokenValue;
136                                        } else if (token.equalsIgnoreCase(PREFIX)) {
137                                                lPrefix = tokenValue;
138                                        } else {
139                                                realUrlBuilder.append(';').append(tokenPairStr);
140                                        }
141                                }
142                        }
143                        realUrl = realUrlBuilder.toString();
144                        realDriver = lRealDriver;
145                        prefix = lPrefix;
146                }
147        }
148
149        /**
150         * Gets value of the specified property.
151         *
152         * @param driverId Driver Id
153         * @param propertyName Property name
154         * @return property value or {@code null}
155         */
156        private static String getProperty(String driverId, String propertyName) {
157                String propertyValue = PROPERTIES.getProperty(DEFAULT_PREFIX + "." + driverId + "." + propertyName);
158                if (propertyValue != null) {
159                        propertyValue = propertyValue.trim();
160                        if (propertyValue.isEmpty()) {
161                                propertyValue = null;
162                        }
163                }
164                return propertyValue;
165        }
166
167        /**
168         * Returns orignal JDBC connection URL without any Simon stuff.
169         *
170         * @return original JDBC connection URL
171         */
172        public String getRealUrl() {
173                return realUrl;
174        }
175
176        /**
177         * Returns driver identifier (eg. oracle, postgres, mysql, h2, etc.).
178         *
179         * @return driver identifier
180         */
181        public String getDriverId() {
182                return driverId;
183        }
184
185        /**
186         * Returns fully qualified class name of the real driver.
187         *
188         * @return driver class FQN
189         */
190        public String getRealDriver() {
191                return realDriver;
192        }
193
194        /**
195         * Returns prefix for hierarchy of JDBC connection related Simons.
196         *
197         * @return prefix for JDBC Simons
198         */
199        public String getPrefix() {
200                return prefix;
201        }
202
203        /**
204         * Simon JDBC connection URL prefixed with {@code jdbc:simon:}.
205         *
206         * @return Simon JDBC connection URL
207         */
208        public String getSimonUrl() {
209                return simonUrl;
210        }
211
212        /**
213         * Tests whether URL is a Simon JDBC connection URL.
214         */
215        public static boolean isSimonUrl(String url) {
216                return url != null && url.toLowerCase().startsWith(SimonConnectionConfiguration.URL_PREFIX);
217        }
218
219        /**
220         * Gets the name of the class implementing {@link javax.sql.ConnectionPoolDataSource}.
221         */
222        public String getRealConnectionPoolDataSourceName() {
223                return getProperty(driverId, "cpdatasource");
224        }
225
226        /**
227         * Gets the name of the class implementing {@link javax.sql.DataSource}.
228         */
229        public String getRealDataSourceName() {
230                return getProperty(driverId, "datasource");
231        }
232
233        /**
234         * Gets the name of the class implementing {@link javax.sql.XADataSource}.
235         */
236        public String getRealXADataSourceName() {
237                return getProperty(driverId, "xadatasource");
238        }
239}