1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.util; |
7 | |
8 | import java.sql.SQLException; |
9 | import java.sql.SQLFeatureNotSupportedException; |
10 | import java.util.Properties; |
11 | |
12 | import javax.sql.ConnectionPoolDataSource; |
13 | import javax.sql.DataSource; |
14 | import javax.sql.XADataSource; |
15 | |
16 | import org.h2.engine.Constants; |
17 | import org.h2.jdbcx.JdbcDataSource; |
18 | import org.osgi.framework.BundleContext; |
19 | import org.osgi.service.jdbc.DataSourceFactory; |
20 | |
21 | /** |
22 | * This class implements the OSGi DataSourceFactory interface for the H2 JDBC |
23 | * driver. The following standard configuration properties are supported: |
24 | * {@link #JDBC_USER}, {@link #JDBC_PASSWORD}, {@link #JDBC_DESCRIPTION}, |
25 | * {@link #JDBC_DATASOURCE_NAME}, {@link #JDBC_NETWORK_PROTOCOL}, |
26 | * {@link #JDBC_URL}, {@link #JDBC_SERVER_NAME}, {@link #JDBC_PORT_NUMBER}. The |
27 | * following standard configuration properties are not supported: |
28 | * {@link #JDBC_ROLE_NAME}, {@link #JDBC_DATABASE_NAME}, |
29 | * {@link #JDBC_INITIAL_POOL_SIZE}, {@link #JDBC_MAX_POOL_SIZE}, |
30 | * {@link #JDBC_MIN_POOL_SIZE}, {@link #JDBC_MAX_IDLE_TIME}, |
31 | * {@link #JDBC_MAX_STATEMENTS}, {@link #JDBC_PROPERTY_CYCLE}. Any other |
32 | * property will be treated as a H2 specific option. If the {@link #JDBC_URL} |
33 | * property is passed to any of the DataSource factories, the following |
34 | * properties will be ignored: {@link #JDBC_DATASOURCE_NAME}, |
35 | * {@link #JDBC_NETWORK_PROTOCOL}, {@link #JDBC_SERVER_NAME}, |
36 | * {@link #JDBC_PORT_NUMBER}. |
37 | * |
38 | * @author Per Otterstrom |
39 | */ |
40 | public class OsgiDataSourceFactory implements DataSourceFactory { |
41 | private org.h2.Driver driver; |
42 | |
43 | public OsgiDataSourceFactory(org.h2.Driver driver) { |
44 | this.driver = driver; |
45 | } |
46 | |
47 | /** |
48 | * Creates a basic data source. |
49 | * |
50 | * @param properties the properties for the data source. |
51 | * @throws SQLException if unsupported properties are supplied, or if data |
52 | * source can not be created. |
53 | * @return a new data source. |
54 | */ |
55 | @Override |
56 | public DataSource createDataSource(Properties properties) |
57 | throws SQLException { |
58 | // Make copy of properties |
59 | Properties propertiesCopy = new Properties(); |
60 | if (properties != null) { |
61 | propertiesCopy.putAll(properties); |
62 | } |
63 | |
64 | // Verify that no unsupported standard options are used |
65 | rejectUnsupportedOptions(propertiesCopy); |
66 | |
67 | // Standard pool properties in OSGi not applicable here |
68 | rejectPoolingOptions(propertiesCopy); |
69 | |
70 | JdbcDataSource dataSource = new JdbcDataSource(); |
71 | |
72 | setupH2DataSource(dataSource, propertiesCopy); |
73 | |
74 | return dataSource; |
75 | } |
76 | |
77 | /** |
78 | * Creates a pooled data source. |
79 | * |
80 | * @param properties the properties for the data source. |
81 | * @throws SQLException if unsupported properties are supplied, or if data |
82 | * source can not be created. |
83 | * @return a new data source. |
84 | */ |
85 | @Override |
86 | public ConnectionPoolDataSource createConnectionPoolDataSource( |
87 | Properties properties) throws SQLException { |
88 | // Make copy of properties |
89 | Properties propertiesCopy = new Properties(); |
90 | if (properties != null) { |
91 | propertiesCopy.putAll(properties); |
92 | } |
93 | |
94 | // Verify that no unsupported standard options are used |
95 | rejectUnsupportedOptions(propertiesCopy); |
96 | |
97 | // The integrated connection pool is H2 is not configurable |
98 | rejectPoolingOptions(propertiesCopy); |
99 | |
100 | JdbcDataSource dataSource = new JdbcDataSource(); |
101 | |
102 | setupH2DataSource(dataSource, propertiesCopy); |
103 | |
104 | return dataSource; |
105 | } |
106 | |
107 | /** |
108 | * Creates a pooled XA data source. |
109 | * |
110 | * @param properties the properties for the data source. |
111 | * @throws SQLException if unsupported properties are supplied, or if data |
112 | * source can not be created. |
113 | * @return a new data source. |
114 | */ |
115 | @Override |
116 | public XADataSource createXADataSource(Properties properties) |
117 | throws SQLException { |
118 | // Make copy of properties |
119 | Properties propertiesCopy = new Properties(); |
120 | if (properties != null) { |
121 | propertiesCopy.putAll(properties); |
122 | } |
123 | |
124 | // Verify that no unsupported standard options are used |
125 | rejectUnsupportedOptions(propertiesCopy); |
126 | |
127 | // The integrated connection pool is H2 is not configurable |
128 | rejectPoolingOptions(propertiesCopy); |
129 | |
130 | JdbcDataSource dataSource = new JdbcDataSource(); |
131 | |
132 | setupH2DataSource(dataSource, propertiesCopy); |
133 | |
134 | return dataSource; |
135 | } |
136 | |
137 | /** |
138 | * Returns a driver. The H2 driver does not support any properties. |
139 | * |
140 | * @param properties must be null or empty list. |
141 | * @throws SQLException if any property is supplied. |
142 | * @return a driver. |
143 | */ |
144 | @Override |
145 | public java.sql.Driver createDriver(Properties properties) |
146 | throws SQLException { |
147 | if (properties != null && !properties.isEmpty()) { |
148 | // No properties supported |
149 | throw new SQLException(); |
150 | } |
151 | return driver; |
152 | } |
153 | |
154 | /** |
155 | * Checker method that will throw if any unsupported standard OSGi options |
156 | * is present. |
157 | * |
158 | * @param p the properties to check |
159 | * @throws SQLFeatureNotSupportedException if unsupported properties are |
160 | * present |
161 | */ |
162 | private static void rejectUnsupportedOptions(Properties p) |
163 | throws SQLFeatureNotSupportedException { |
164 | // Unsupported standard properties in OSGi |
165 | if (p.containsKey(DataSourceFactory.JDBC_ROLE_NAME)) { |
166 | throw new SQLFeatureNotSupportedException("The " + |
167 | DataSourceFactory.JDBC_ROLE_NAME + |
168 | " property is not supported by H2"); |
169 | } |
170 | if (p.containsKey(DataSourceFactory.JDBC_DATASOURCE_NAME)) { |
171 | throw new SQLFeatureNotSupportedException("The " + |
172 | DataSourceFactory.JDBC_DATASOURCE_NAME + |
173 | " property is not supported by H2"); |
174 | } |
175 | } |
176 | |
177 | /** |
178 | * Applies common OSGi properties to a H2 data source. Non standard |
179 | * properties will be applied as H2 options. |
180 | * |
181 | * @param dataSource the data source to configure |
182 | * @param p the properties to apply to the data source |
183 | */ |
184 | private static void setupH2DataSource(JdbcDataSource dataSource, |
185 | Properties p) { |
186 | // Setting user and password |
187 | if (p.containsKey(DataSourceFactory.JDBC_USER)) { |
188 | dataSource.setUser((String) p.remove(DataSourceFactory.JDBC_USER)); |
189 | } |
190 | if (p.containsKey(DataSourceFactory.JDBC_PASSWORD)) { |
191 | dataSource.setPassword((String) p |
192 | .remove(DataSourceFactory.JDBC_PASSWORD)); |
193 | } |
194 | |
195 | // Setting description |
196 | if (p.containsKey(DataSourceFactory.JDBC_DESCRIPTION)) { |
197 | dataSource.setDescription((String) p |
198 | .remove(DataSourceFactory.JDBC_DESCRIPTION)); |
199 | } |
200 | |
201 | // Setting URL |
202 | StringBuffer connectionUrl = new StringBuffer(); |
203 | if (p.containsKey(DataSourceFactory.JDBC_URL)) { |
204 | // Use URL if specified |
205 | connectionUrl.append(p.remove(DataSourceFactory.JDBC_URL)); |
206 | // Remove individual properties |
207 | p.remove(DataSourceFactory.JDBC_NETWORK_PROTOCOL); |
208 | p.remove(DataSourceFactory.JDBC_SERVER_NAME); |
209 | p.remove(DataSourceFactory.JDBC_PORT_NUMBER); |
210 | p.remove(DataSourceFactory.JDBC_DATABASE_NAME); |
211 | } else { |
212 | // Creating URL from individual properties |
213 | connectionUrl.append(Constants.START_URL); |
214 | |
215 | // Set network protocol (tcp/ssl) or DB type (mem/file) |
216 | String protocol = ""; |
217 | if (p.containsKey(DataSourceFactory.JDBC_NETWORK_PROTOCOL)) { |
218 | protocol = (String) p.remove(DataSourceFactory.JDBC_NETWORK_PROTOCOL); |
219 | connectionUrl.append(protocol).append(":"); |
220 | } |
221 | |
222 | // Host name and/or port |
223 | if (p.containsKey(DataSourceFactory.JDBC_SERVER_NAME)) { |
224 | connectionUrl.append("//").append( |
225 | p.remove(DataSourceFactory.JDBC_SERVER_NAME)); |
226 | |
227 | if (p.containsKey(DataSourceFactory.JDBC_PORT_NUMBER)) { |
228 | connectionUrl.append(":").append( |
229 | p.remove(DataSourceFactory.JDBC_PORT_NUMBER)); |
230 | } |
231 | |
232 | connectionUrl.append("/"); |
233 | } else if (p.containsKey( |
234 | DataSourceFactory.JDBC_PORT_NUMBER)) { |
235 | // Assume local host if only port was set |
236 | connectionUrl |
237 | .append("//localhost:") |
238 | .append(p.remove(DataSourceFactory.JDBC_PORT_NUMBER)) |
239 | .append("/"); |
240 | } else if (protocol.equals("tcp") || protocol.equals("ssl")) { |
241 | // Assume local host if network protocol is set, but no host or |
242 | // port is set |
243 | connectionUrl.append("//localhost/"); |
244 | } |
245 | |
246 | // DB path and name |
247 | if (p.containsKey(DataSourceFactory.JDBC_DATABASE_NAME)) { |
248 | connectionUrl.append( |
249 | p.remove(DataSourceFactory.JDBC_DATABASE_NAME)); |
250 | } |
251 | } |
252 | |
253 | // Add remaining properties as options |
254 | for (Object option : p.keySet()) { |
255 | connectionUrl.append(";").append(option).append("=") |
256 | .append(p.get(option)); |
257 | } |
258 | |
259 | if (connectionUrl.length() > Constants.START_URL.length()) { |
260 | dataSource.setURL(connectionUrl.toString()); |
261 | } |
262 | } |
263 | |
264 | /** |
265 | * Checker method that will throw if any pooling related standard OSGi |
266 | * options are present. |
267 | * |
268 | * @param p the properties to check |
269 | * @throws SQLFeatureNotSupportedException if unsupported properties are |
270 | * present |
271 | */ |
272 | private static void rejectPoolingOptions(Properties p) |
273 | throws SQLFeatureNotSupportedException { |
274 | if (p.containsKey(DataSourceFactory.JDBC_INITIAL_POOL_SIZE) || |
275 | p.containsKey(DataSourceFactory.JDBC_MAX_IDLE_TIME) || |
276 | p.containsKey(DataSourceFactory.JDBC_MAX_POOL_SIZE) || |
277 | p.containsKey(DataSourceFactory.JDBC_MAX_STATEMENTS) || |
278 | p.containsKey(DataSourceFactory.JDBC_MIN_POOL_SIZE) || |
279 | p.containsKey(DataSourceFactory.JDBC_PROPERTY_CYCLE)) { |
280 | throw new SQLFeatureNotSupportedException( |
281 | "Pooling properties are not supported by H2"); |
282 | } |
283 | } |
284 | |
285 | /** |
286 | * Register the H2 JDBC driver service. |
287 | * |
288 | * @param bundleContext the bundle context |
289 | * @param driver the driver |
290 | */ |
291 | static void registerService(BundleContext bundleContext, |
292 | org.h2.Driver driver) { |
293 | Properties properties = new Properties(); |
294 | properties.put( |
295 | DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, |
296 | org.h2.Driver.class.getName()); |
297 | properties.put( |
298 | DataSourceFactory.OSGI_JDBC_DRIVER_NAME, |
299 | "H2 JDBC Driver"); |
300 | properties.put( |
301 | DataSourceFactory.OSGI_JDBC_DRIVER_VERSION, |
302 | Constants.getFullVersion()); |
303 | bundleContext.registerService( |
304 | DataSourceFactory.class.getName(), |
305 | new OsgiDataSourceFactory(driver), properties); |
306 | } |
307 | } |