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.io.ByteArrayInputStream; |
9 | import java.io.ByteArrayOutputStream; |
10 | import java.io.IOException; |
11 | import java.io.ObjectInputStream; |
12 | import java.io.ObjectOutputStream; |
13 | import java.io.ObjectStreamClass; |
14 | import java.sql.Connection; |
15 | import java.sql.DriverManager; |
16 | import java.sql.ResultSet; |
17 | import java.sql.SQLException; |
18 | import java.sql.Statement; |
19 | import java.util.ArrayList; |
20 | import java.util.HashSet; |
21 | import java.util.Properties; |
22 | |
23 | import javax.naming.Context; |
24 | import javax.sql.DataSource; |
25 | |
26 | import org.h2.api.ErrorCode; |
27 | import org.h2.api.JavaObjectSerializer; |
28 | import org.h2.engine.SysProperties; |
29 | import org.h2.message.DbException; |
30 | import org.h2.store.DataHandler; |
31 | import org.h2.util.Utils.ClassFactory; |
32 | |
33 | /** |
34 | * This is a utility class with JDBC helper functions. |
35 | */ |
36 | public class JdbcUtils { |
37 | |
38 | /** |
39 | * The serializer to use. |
40 | */ |
41 | public static JavaObjectSerializer serializer; |
42 | |
43 | private static final String[] DRIVERS = { |
44 | "h2:", "org.h2.Driver", |
45 | "Cache:", "com.intersys.jdbc.CacheDriver", |
46 | "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver", |
47 | "daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", |
48 | "db2:", "COM.ibm.db2.jdbc.net.DB2Driver", |
49 | "derby:net:", "org.apache.derby.jdbc.ClientDriver", |
50 | "derby://", "org.apache.derby.jdbc.ClientDriver", |
51 | "derby:", "org.apache.derby.jdbc.EmbeddedDriver", |
52 | "FrontBase:", "com.frontbase.jdbc.FBJDriver", |
53 | "firebirdsql:", "org.firebirdsql.jdbc.FBDriver", |
54 | "hsqldb:", "org.hsqldb.jdbcDriver", |
55 | "informix-sqli:", "com.informix.jdbc.IfxDriver", |
56 | "jtds:", "net.sourceforge.jtds.jdbc.Driver", |
57 | "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver", |
58 | "mimer:", "com.mimer.jdbc.Driver", |
59 | "mysql:", "com.mysql.jdbc.Driver", |
60 | "odbc:", "sun.jdbc.odbc.JdbcOdbcDriver", |
61 | "oracle:", "oracle.jdbc.driver.OracleDriver", |
62 | "pervasive:", "com.pervasive.jdbc.v2.Driver", |
63 | "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver", |
64 | "pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", |
65 | "postgresql:", "org.postgresql.Driver", |
66 | "sybase:", "com.sybase.jdbc3.jdbc.SybDriver", |
67 | "sqlserver:", "com.microsoft.sqlserver.jdbc.SQLServerDriver", |
68 | "teradata:", "com.ncr.teradata.TeraDriver", |
69 | }; |
70 | |
71 | private static boolean allowAllClasses; |
72 | private static HashSet<String> allowedClassNames; |
73 | |
74 | /** |
75 | * In order to manage more than one class loader |
76 | */ |
77 | private static ArrayList<ClassFactory> userClassFactories = |
78 | new ArrayList<ClassFactory>(); |
79 | |
80 | private static String[] allowedClassNamePrefixes; |
81 | |
82 | private JdbcUtils() { |
83 | // utility class |
84 | } |
85 | |
86 | /** |
87 | * Add a class factory in order to manage more than one class loader. |
88 | * |
89 | * @param classFactory An object that implements ClassFactory |
90 | */ |
91 | public static void addClassFactory(ClassFactory classFactory) { |
92 | getUserClassFactories().add(classFactory); |
93 | } |
94 | |
95 | /** |
96 | * Remove a class factory |
97 | * |
98 | * @param classFactory Already inserted class factory instance |
99 | */ |
100 | public static void removeClassFactory(ClassFactory classFactory) { |
101 | getUserClassFactories().remove(classFactory); |
102 | } |
103 | |
104 | private static ArrayList<ClassFactory> getUserClassFactories() { |
105 | if (userClassFactories == null) { |
106 | // initially, it is empty |
107 | // but Apache Tomcat may clear the fields as well |
108 | userClassFactories = new ArrayList<ClassFactory>(); |
109 | } |
110 | return userClassFactories; |
111 | } |
112 | |
113 | static { |
114 | String clazz = SysProperties.JAVA_OBJECT_SERIALIZER; |
115 | if (clazz != null) { |
116 | try { |
117 | serializer = (JavaObjectSerializer) loadUserClass(clazz).newInstance(); |
118 | } catch (Exception e) { |
119 | throw DbException.convert(e); |
120 | } |
121 | } |
122 | } |
123 | |
124 | /** |
125 | * Load a class, but check if it is allowed to load this class first. To |
126 | * perform access rights checking, the system property h2.allowedClasses |
127 | * needs to be set to a list of class file name prefixes. |
128 | * |
129 | * @param className the name of the class |
130 | * @return the class object |
131 | */ |
132 | public static Class<?> loadUserClass(String className) { |
133 | if (allowedClassNames == null) { |
134 | // initialize the static fields |
135 | String s = SysProperties.ALLOWED_CLASSES; |
136 | ArrayList<String> prefixes = New.arrayList(); |
137 | boolean allowAll = false; |
138 | HashSet<String> classNames = New.hashSet(); |
139 | for (String p : StringUtils.arraySplit(s, ',', true)) { |
140 | if (p.equals("*")) { |
141 | allowAll = true; |
142 | } else if (p.endsWith("*")) { |
143 | prefixes.add(p.substring(0, p.length() - 1)); |
144 | } else { |
145 | classNames.add(p); |
146 | } |
147 | } |
148 | allowedClassNamePrefixes = new String[prefixes.size()]; |
149 | prefixes.toArray(allowedClassNamePrefixes); |
150 | allowAllClasses = allowAll; |
151 | allowedClassNames = classNames; |
152 | } |
153 | if (!allowAllClasses && !allowedClassNames.contains(className)) { |
154 | boolean allowed = false; |
155 | for (String s : allowedClassNamePrefixes) { |
156 | if (className.startsWith(s)) { |
157 | allowed = true; |
158 | } |
159 | } |
160 | if (!allowed) { |
161 | throw DbException.get( |
162 | ErrorCode.ACCESS_DENIED_TO_CLASS_1, className); |
163 | } |
164 | } |
165 | // Use provided class factory first. |
166 | for (ClassFactory classFactory : getUserClassFactories()) { |
167 | if (classFactory.match(className)) { |
168 | try { |
169 | Class<?> userClass = classFactory.loadClass(className); |
170 | if (!(userClass == null)) { |
171 | return userClass; |
172 | } |
173 | } catch (Exception e) { |
174 | throw DbException.get( |
175 | ErrorCode.CLASS_NOT_FOUND_1, e, className); |
176 | } |
177 | } |
178 | } |
179 | // Use local ClassLoader |
180 | try { |
181 | return Class.forName(className); |
182 | } catch (ClassNotFoundException e) { |
183 | try { |
184 | return Class.forName( |
185 | className, true, |
186 | Thread.currentThread().getContextClassLoader()); |
187 | } catch (Exception e2) { |
188 | throw DbException.get( |
189 | ErrorCode.CLASS_NOT_FOUND_1, e, className); |
190 | } |
191 | } catch (NoClassDefFoundError e) { |
192 | throw DbException.get( |
193 | ErrorCode.CLASS_NOT_FOUND_1, e, className); |
194 | } catch (Error e) { |
195 | // UnsupportedClassVersionError |
196 | throw DbException.get( |
197 | ErrorCode.GENERAL_ERROR_1, e, className); |
198 | } |
199 | } |
200 | |
201 | /** |
202 | * Close a statement without throwing an exception. |
203 | * |
204 | * @param stat the statement or null |
205 | */ |
206 | public static void closeSilently(Statement stat) { |
207 | if (stat != null) { |
208 | try { |
209 | stat.close(); |
210 | } catch (SQLException e) { |
211 | // ignore |
212 | } |
213 | } |
214 | } |
215 | |
216 | /** |
217 | * Close a connection without throwing an exception. |
218 | * |
219 | * @param conn the connection or null |
220 | */ |
221 | public static void closeSilently(Connection conn) { |
222 | if (conn != null) { |
223 | try { |
224 | conn.close(); |
225 | } catch (SQLException e) { |
226 | // ignore |
227 | } |
228 | } |
229 | } |
230 | |
231 | /** |
232 | * Close a result set without throwing an exception. |
233 | * |
234 | * @param rs the result set or null |
235 | */ |
236 | public static void closeSilently(ResultSet rs) { |
237 | if (rs != null) { |
238 | try { |
239 | rs.close(); |
240 | } catch (SQLException e) { |
241 | // ignore |
242 | } |
243 | } |
244 | } |
245 | |
246 | /** |
247 | * Open a new database connection with the given settings. |
248 | * |
249 | * @param driver the driver class name |
250 | * @param url the database URL |
251 | * @param user the user name |
252 | * @param password the password |
253 | * @return the database connection |
254 | */ |
255 | public static Connection getConnection(String driver, String url, |
256 | String user, String password) throws SQLException { |
257 | Properties prop = new Properties(); |
258 | if (user != null) { |
259 | prop.setProperty("user", user); |
260 | } |
261 | if (password != null) { |
262 | prop.setProperty("password", password); |
263 | } |
264 | return getConnection(driver, url, prop); |
265 | } |
266 | |
267 | /** |
268 | * Open a new database connection with the given settings. |
269 | * |
270 | * @param driver the driver class name |
271 | * @param url the database URL |
272 | * @param prop the properties containing at least the user name and password |
273 | * @return the database connection |
274 | */ |
275 | public static Connection getConnection(String driver, String url, |
276 | Properties prop) throws SQLException { |
277 | if (StringUtils.isNullOrEmpty(driver)) { |
278 | JdbcUtils.load(url); |
279 | } else { |
280 | Class<?> d = loadUserClass(driver); |
281 | if (java.sql.Driver.class.isAssignableFrom(d)) { |
282 | return DriverManager.getConnection(url, prop); |
283 | } else if (javax.naming.Context.class.isAssignableFrom(d)) { |
284 | // JNDI context |
285 | try { |
286 | Context context = (Context) d.newInstance(); |
287 | DataSource ds = (DataSource) context.lookup(url); |
288 | String user = prop.getProperty("user"); |
289 | String password = prop.getProperty("password"); |
290 | if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) { |
291 | return ds.getConnection(); |
292 | } |
293 | return ds.getConnection(user, password); |
294 | } catch (Exception e) { |
295 | throw DbException.toSQLException(e); |
296 | } |
297 | } else { |
298 | // don't know, but maybe it loaded a JDBC Driver |
299 | return DriverManager.getConnection(url, prop); |
300 | } |
301 | } |
302 | return DriverManager.getConnection(url, prop); |
303 | } |
304 | |
305 | /** |
306 | * Get the driver class name for the given URL, or null if the URL is |
307 | * unknown. |
308 | * |
309 | * @param url the database URL |
310 | * @return the driver class name |
311 | */ |
312 | public static String getDriver(String url) { |
313 | if (url.startsWith("jdbc:")) { |
314 | url = url.substring("jdbc:".length()); |
315 | for (int i = 0; i < DRIVERS.length; i += 2) { |
316 | String prefix = DRIVERS[i]; |
317 | if (url.startsWith(prefix)) { |
318 | return DRIVERS[i + 1]; |
319 | } |
320 | } |
321 | } |
322 | return null; |
323 | } |
324 | |
325 | /** |
326 | * Load the driver class for the given URL, if the database URL is known. |
327 | * |
328 | * @param url the database URL |
329 | */ |
330 | public static void load(String url) { |
331 | String driver = getDriver(url); |
332 | if (driver != null) { |
333 | loadUserClass(driver); |
334 | } |
335 | } |
336 | |
337 | /** |
338 | * Serialize the object to a byte array, using the serializer specified by |
339 | * the connection info if set, or the default serializer. |
340 | * |
341 | * @param obj the object to serialize |
342 | * @param dataHandler provides the object serializer (may be null) |
343 | * @return the byte array |
344 | */ |
345 | public static byte[] serialize(Object obj, DataHandler dataHandler) { |
346 | try { |
347 | JavaObjectSerializer handlerSerializer = null; |
348 | if (dataHandler != null) { |
349 | handlerSerializer = dataHandler.getJavaObjectSerializer(); |
350 | } |
351 | if (handlerSerializer != null) { |
352 | return handlerSerializer.serialize(obj); |
353 | } |
354 | if (serializer != null) { |
355 | return serializer.serialize(obj); |
356 | } |
357 | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
358 | ObjectOutputStream os = new ObjectOutputStream(out); |
359 | os.writeObject(obj); |
360 | return out.toByteArray(); |
361 | } catch (Throwable e) { |
362 | throw DbException.get(ErrorCode.SERIALIZATION_FAILED_1, e, e.toString()); |
363 | } |
364 | } |
365 | |
366 | /** |
367 | * De-serialize the byte array to an object, eventually using the serializer |
368 | * specified by the connection info. |
369 | * |
370 | * @param data the byte array |
371 | * @param dataHandler provides the object serializer (may be null) |
372 | * @return the object |
373 | * @throws DbException if serialization fails |
374 | */ |
375 | public static Object deserialize(byte[] data, DataHandler dataHandler) { |
376 | try { |
377 | JavaObjectSerializer dbJavaObjectSerializer = null; |
378 | if (dataHandler != null) { |
379 | dbJavaObjectSerializer = dataHandler.getJavaObjectSerializer(); |
380 | } |
381 | if (dbJavaObjectSerializer != null) { |
382 | return dbJavaObjectSerializer.deserialize(data); |
383 | } |
384 | if (serializer != null) { |
385 | return serializer.deserialize(data); |
386 | } |
387 | ByteArrayInputStream in = new ByteArrayInputStream(data); |
388 | ObjectInputStream is; |
389 | if (SysProperties.USE_THREAD_CONTEXT_CLASS_LOADER) { |
390 | final ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
391 | is = new ObjectInputStream(in) { |
392 | @Override |
393 | protected Class<?> resolveClass(ObjectStreamClass desc) |
394 | throws IOException, ClassNotFoundException { |
395 | try { |
396 | return Class.forName(desc.getName(), true, loader); |
397 | } catch (ClassNotFoundException e) { |
398 | return super.resolveClass(desc); |
399 | } |
400 | } |
401 | }; |
402 | } else { |
403 | is = new ObjectInputStream(in); |
404 | } |
405 | return is.readObject(); |
406 | } catch (Throwable e) { |
407 | throw DbException.get(ErrorCode.DESERIALIZATION_FAILED_1, e, e.toString()); |
408 | } |
409 | } |
410 | |
411 | } |