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 package org.apache.openjpa.jdbc.schema;
20
21 import java.security.AccessController;
22 import java.sql.Connection;
23 import java.sql.Driver;
24 import java.sql.SQLException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Iterator;
28 import java.util.List;
29 import javax.sql.DataSource;
30
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
33 import org.apache.openjpa.jdbc.sql.DBDictionary;
34 import org.apache.openjpa.lib.conf.Configurations;
35 import org.apache.openjpa.lib.jdbc.ConfiguringConnectionDecorator;
36 import org.apache.openjpa.lib.jdbc.ConnectionDecorator;
37 import org.apache.openjpa.lib.jdbc.DecoratingDataSource;
38 import org.apache.openjpa.lib.jdbc.DelegatingDataSource;
39 import org.apache.openjpa.lib.jdbc.JDBCEventConnectionDecorator;
40 import org.apache.openjpa.lib.jdbc.JDBCListener;
41 import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
42 import org.apache.openjpa.lib.log.Log;
43 import org.apache.openjpa.lib.util.J2DoPrivHelper;
44 import org.apache.openjpa.lib.util.Localizer;
45 import org.apache.openjpa.lib.util.Options;
46 import org.apache.openjpa.util.ImplHelper;
47 import org.apache.openjpa.util.OpenJPAException;
48 import org.apache.openjpa.util.StoreException;
49 import org.apache.openjpa.util.UserException;
50
51 /**
52 * Factory for {@link DataSource} objects. The factory uses the supplied
53 * configuration to obtain a 3rd-party datasource or to create one, and
54 * to setup prepared statement caching.
55 *
56 * @author Abe White
57 * @nojavadoc
58 */
59 public class DataSourceFactory {
60
61 private static final Localizer _loc = Localizer.forPackage
62 (DataSourceFactory.class);
63
64 /**
65 * Create a datasource using the given configuration.
66 */
67 public static DataSource newDataSource(JDBCConfiguration conf,
68 boolean factory2) {
69 String driver = (factory2) ? conf.getConnection2DriverName()
70 : conf.getConnectionDriverName();
71 if (StringUtils.isEmpty(driver))
72 throw new UserException(_loc.get("no-driver", driver)).
73 setFatal(true);
74
75 ClassLoader loader = conf.getClassResolverInstance().
76 getClassLoader(DataSourceFactory.class, null);
77 String props = (factory2) ? conf.getConnection2Properties()
78 : conf.getConnectionProperties();
79 try {
80 Class driverClass;
81 try {
82 driverClass = Class.forName(driver, true, loader);
83 } catch (ClassNotFoundException cnfe) {
84 // try with the core class loader
85 driverClass = Class.forName(driver);
86 }
87
88 if (Driver.class.isAssignableFrom(driverClass)) {
89 DriverDataSource ds = conf.newDriverDataSourceInstance();
90 ds.setClassLoader(loader);
91 ds.setConnectionDriverName(driver);
92 ds.setConnectionProperties(Configurations.
93 parseProperties(props));
94
95 if (!factory2) {
96 ds.setConnectionFactoryProperties(Configurations.
97 parseProperties(conf.getConnectionFactoryProperties()));
98 ds.setConnectionURL(conf.getConnectionURL());
99 ds.setConnectionUserName(conf.getConnectionUserName());
100 ds.setConnectionPassword(conf.getConnectionPassword());
101 } else {
102 ds.setConnectionFactoryProperties
103 (Configurations.parseProperties(conf.
104 getConnectionFactory2Properties()));
105 ds.setConnectionURL(conf.getConnection2URL());
106 ds.setConnectionUserName(conf.getConnection2UserName());
107 ds.setConnectionPassword(conf.getConnection2Password());
108 }
109 return ds;
110 }
111
112 // see if their driver name is actually a data source
113 if (DataSource.class.isAssignableFrom(driverClass)) {
114 return (DataSource) Configurations.newInstance(driver,
115 conf, props, (ClassLoader) AccessController.doPrivileged(
116 J2DoPrivHelper.getClassLoaderAction(
117 DataSource.class)));
118 }
119 }
120 catch (OpenJPAException ke) {
121 throw ke;
122 } catch (Exception e) {
123 throw new StoreException(e).setFatal(true);
124 }
125
126 // not a driver or a data source; die
127 throw new UserException(_loc.get("bad-driver", driver)).setFatal(true);
128 }
129
130 /**
131 * Install listeners and base decorators.
132 */
133 public static DecoratingDataSource decorateDataSource(DataSource ds,
134 JDBCConfiguration conf, boolean factory2) {
135 Options opts = Configurations.parseProperties((factory2)
136 ? conf.getConnectionFactory2Properties()
137 : conf.getConnectionFactoryProperties());
138 Log jdbcLog = conf.getLog(JDBCConfiguration.LOG_JDBC);
139 Log sqlLog = conf.getLog(JDBCConfiguration.LOG_SQL);
140
141 DecoratingDataSource dds = new DecoratingDataSource(ds);
142 try {
143 // add user-defined decorators
144 List decorators = new ArrayList();
145 decorators.addAll(Arrays.asList(conf.
146 getConnectionDecoratorInstances()));
147
148 // add jdbc events decorator
149 JDBCEventConnectionDecorator ecd =
150 new JDBCEventConnectionDecorator();
151 Configurations.configureInstance(ecd, conf, opts);
152 JDBCListener[] listeners = conf.getJDBCListenerInstances();
153 for (int i = 0; i < listeners.length; i++)
154 ecd.addListener(listeners[i]);
155 decorators.add(ecd);
156
157 // ask the DriverDataSource to provide any additional decorators
158 if (ds instanceof DriverDataSource) {
159 List decs = ((DriverDataSource) ds).
160 createConnectionDecorators();
161 if (decs != null)
162 decorators.addAll(decs);
163 }
164
165 // logging decorator
166 LoggingConnectionDecorator lcd =
167 new LoggingConnectionDecorator();
168 Configurations.configureInstance(lcd, conf, opts);
169 lcd.getLogs().setJDBCLog(jdbcLog);
170 lcd.getLogs().setSQLLog(sqlLog);
171 decorators.add(lcd);
172
173 dds.addDecorators(decorators);
174 return dds;
175 } catch (OpenJPAException ke) {
176 throw ke;
177 } catch (Exception e) {
178 throw new StoreException(e).setFatal(true);
179 }
180 }
181
182 /**
183 * Install things deferred until the DBDictionary instance is available.
184 *
185 * @author Steve Kim
186 */
187 public static DecoratingDataSource installDBDictionary(DBDictionary dict,
188 DecoratingDataSource ds, final JDBCConfiguration conf,
189 boolean factory2) {
190 DataSource inner = ds.getInnermostDelegate();
191 if (inner instanceof DriverDataSource)
192 ((DriverDataSource) inner).initDBDictionary(dict);
193 Connection conn = null;
194
195 try {
196 // add the dictionary as a warning handler on the logging
197 // decorator
198 ConnectionDecorator cd;
199 for (Iterator itr = ds.getDecorators().iterator(); itr.hasNext();) {
200 cd = (ConnectionDecorator) itr.next();
201 if (cd instanceof LoggingConnectionDecorator)
202 ((LoggingConnectionDecorator) cd).setWarningHandler(dict);
203 }
204
205 // misc configuration connection decorator (statement timeouts,
206 // transaction isolation, etc)
207 ConfiguringConnectionDecorator ccd =
208 new ConfiguringConnectionDecorator();
209 ccd.setTransactionIsolation(conf.getTransactionIsolationConstant());
210 if (factory2 || !conf.isConnectionFactoryModeManaged()) {
211 if (!dict.supportsMultipleNontransactionalResultSets)
212 ccd.setAutoCommit(Boolean.FALSE);
213 else
214 ccd.setAutoCommit(Boolean.TRUE);
215 }
216 Options opts = Configurations.parseProperties((factory2)
217 ? conf.getConnectionFactory2Properties()
218 : conf.getConnectionFactoryProperties());
219 Configurations.configureInstance(ccd, conf, opts);
220 ds.addDecorator(ccd);
221
222 // allow the dbdictionary to decorate the connection further
223 ds.addDecorator(dict);
224
225 // ensure dbdictionary to process connectedConfiguration()
226 if (!factory2)
227 conn = ds.getConnection(conf.getConnectionUserName(), conf
228 .getConnectionPassword());
229 else
230 conn = ds.getConnection(conf.getConnection2UserName(), conf
231 .getConnection2Password());
232
233 return ds;
234 } catch (Exception e) {
235 throw new StoreException(e).setFatal(true);
236 } finally {
237 if (conn != null)
238 try {
239 conn.close();
240 } catch (SQLException se) {
241 // ignore any exception since the connection is not going
242 // to be used anyway
243 }
244 }
245 }
246
247 /**
248 * Return a data source with the given user name and password
249 * pre-configured as the defaults when {@link DataSource#getConnection}
250 * is called.
251 */
252 public static DataSource defaultsDataSource(DataSource ds,
253 String user, String pass) {
254 if (user == null && pass == null)
255 return ds;
256 // also check if they are both blank strings
257 if ("".equals(user) && "".equals(pass))
258 return ds;
259 return new DefaultsDataSource(ds, user, pass);
260 }
261
262 /**
263 * Close the given data source.
264 */
265 public static void closeDataSource(DataSource ds) {
266 if (ds instanceof DelegatingDataSource)
267 ds = ((DelegatingDataSource) ds).getInnermostDelegate();
268 ImplHelper.close(ds);
269 }
270
271 /**
272 * A data source with pre-configured default user name and password.
273 */
274 private static class DefaultsDataSource
275 extends DelegatingDataSource {
276
277 private final String _user;
278 private final String _pass;
279
280 public DefaultsDataSource(DataSource ds, String user, String pass) {
281 super(ds);
282 _user = user;
283 _pass = pass;
284 }
285
286 public Connection getConnection()
287 throws SQLException {
288 return super.getConnection(_user, _pass);
289 }
290
291 public Connection getConnection(String user, String pass)
292 throws SQLException {
293 return super.getConnection(user, pass);
294 }
295 }
296 }