001 package hep.aida.ref.sql;
002
003 // Aida
004 import hep.aida.ref.AidaUtils;
005
006 // Java
007 import java.util.Map;
008 import java.util.HashMap;
009
010 // SQL
011 import java.sql.DriverManager;
012 import java.sql.Connection;
013 import java.sql.SQLException;
014
015 // Log4J
016 import org.apache.log4j.Logger;
017
018 /** <code>Accessor</code> provides access to RDBMS.
019 * Following properties are required:
020 * <ul>
021 * <li><code>hep.aida.ref.sql.db</code></li>
022 * <li><code>hep.aida.ref.sql.schema</code>, optional</li>
023 * <li><code>hep.aida.ref.sql.name</code>, optional</li>
024 * <li><code>hep.aida.ref.sql.src</code>,
025 * default = <code>hep/aida/ref/sql/StmtSrc.properties</code></li>
026 * <li><code>hep.aida.ref.sql.user</code></li>
027 * <li><code>hep.aida.ref.sql.passwd</code></li>
028 * </ul>
029 * Static Accessor functions form an Accesor Factory. It tries to
030 * reuse already created Accessors if they share the same characteristics
031 * (options).
032 * <p><font color="#880088">
033 * <pre>
034 * $Log: Accessor.java,v $
035 * Revision 1.54 2007/09/26 07:58:51 hrivnac
036 * cache savable fromjas menu
037 *
038 * Revision 1.53 2007/05/23 16:38:43 hrivnac
039 * logical connections for Plotter; better UML
040 *
041 * Revision 1.52 2007/05/22 23:33:20 hrivnac
042 * Plotter uses real arguments parsing
043 *
044 * Revision 1.51 2006/12/15 00:16:13 hrivnac
045 * FORWARD_ONLY allows to plot big ntuples
046 *
047 * Revision 1.50 2006/12/12 15:21:42 hrivnac
048 * McKoi replaced with Derby; uppercase-only dbs handled better; moving to JAS 0.8.3
049 *
050 * Revision 1.49 2006/11/20 13:51:59 hrivnac
051 * Connection is closed except within JAS3
052 *
053 * Revision 1.48 2006/11/15 16:16:01 hrivnac
054 * Oracle work fine in CERN, cache enabled
055 *
056 * Revision 1.47 2004/12/07 01:04:56 hrivnac
057 * name pattern can be specified
058 *
059 * Revision 1.46 2004/12/01 16:07:15 hrivnac
060 * better support for ColMan JAS3 plugin
061 *
062 * Revision 1.45 2004/11/24 14:32:32 hrivnac
063 * better support for schemas
064 *
065 * Revision 1.44 2004/11/22 18:09:52 hrivnac
066 * Oracle treatment
067 *
068 * Revision 1.43 2004/10/29 22:27:25 hrivnac
069 * imports corrected
070 *
071 * Revision 1.42 2004/10/27 14:19:03 hrivnac
072 * small bug fixes
073 *
074 * Revision 1.41 2004/10/22 15:32:59 hrivnac
075 * cleaned
076 *
077 * Revision 1.40 2004/10/22 14:22:44 hrivnac
078 * properties loading refactored
079 *
080 * Revision 1.39 2004/10/20 23:02:29 hrivnac
081 * Types mapping simplified
082 *
083 * Revision 1.38 2004/10/12 13:25:40 hrivnac
084 * small improvements
085 *
086 * Revision 1.37 2004/10/08 15:22:33 hrivnac
087 * JAS3 plugin works
088 *
089 * Revision 1.36 2004/05/22 15:12:59 hrivnac
090 * class id reformated
091 *
092 * Revision 1.35 2004/04/16 15:32:57 hrivnac
093 * Type.properties to setup Type.class
094 *
095 * Revision 1.34 2004/04/14 13:54:41 hrivnac
096 * potential bugs fixed
097 *
098 * Revision 1.33 2004/04/14 13:39:46 hrivnac
099 * 1.5 warnings fixed
100 *
101 * Revision 1.32 2004/04/13 15:45:54 hrivnac
102 * AIDA URL introduced
103 *
104 * Revision 1.31 2004/03/10 13:55:57 hrivnac
105 * *** empty log message ***
106 *
107 * Revision 1.30 2004/03/10 13:55:15 hrivnac
108 * *** empty log message ***
109 *
110 * Revision 1.29 2004/02/12 16:39:40 hrivnac
111 * niceing
112 *
113 * Revision 1.28 2004/02/10 14:50:58 hrivnac
114 * JavaDoc tags completed
115 *
116 * Revision 1.27 2004/02/04 13:30:39 hrivnac
117 * - improvement of Enums internal mapping
118 * - general cleaning
119 *
120 * Revision 1.26 2004/01/30 18:10:30 hrivnac
121 * - StmtSrc.properties specified a Property
122 * - customised FreeHEP AIDA services included in jar file
123 *
124 * Revision 1.25 2003/11/20 17:21:57 hrivnac
125 * Java 1.5 natively supported, Log4J reporting improved.
126 *
127 * Revision 1.24 2003/11/17 10:18:28 hrivnac
128 * Cleaning.
129 *
130 * Revision 1.23 2003/11/05 19:46:22 hrivnac
131 * - FreeHEP 1.2.1
132 * - JAIDA 3.2.1
133 *
134 * Revision 1.10 2003/10/10 13:21:36 hrivnac
135 * Works between different DB technologies.
136 *
137 * Revision 1.9 2003/10/10 10:12:21 hrivnac
138 * All three testing pgms work.
139 *
140 * Revision 1.5 2003/10/03 16:02:21 hrivnac
141 * Better handling of multiple technologies.
142 *
143 * Revision 1.3 2003/10/02 14:24:00 hrivnac
144 * Cleaning.
145 *
146 * Revision 1.2 2003/10/02 10:00:40 hrivnac
147 * Cleaning.
148 *
149 * </pre>
150 * </font></p>
151 * @opt attributes
152 * @opt operations
153 * @opt types
154 * @opt visibility
155 * @version $Id: Accessor.java,v 1.54 2007/09/26 07:58:51 hrivnac Exp $
156 * @author <a href="mailto:Julius.Hrivnac@cern.ch">J.Hrivnac</a> */
157 public class Accessor {
158
159 /** Get Accesor corresponding to optionsMap if it already exists.
160 * Creates new Accessor otherwise.
161 * @param optionsMap The map of options to define the Accessor.
162 * @return The Accessor associated to specified optionsMap. */
163 public static Accessor getAccessor(Map optionsMap) throws SQLTupleException {
164 if (_accessors.containsKey(optionsMap)) {
165 log.debug("Accessor reused: " + optionsMap);
166 return _accessors.get(optionsMap);
167 }
168 else {
169 log.debug("Accessor created: " + optionsMap);
170 Accessor accessor = new Accessor(optionsMap);
171 _accessors.put(optionsMap, accessor);
172 return accessor;
173 }
174 }
175
176 /** Get Accesor corresponding to optionsString if it already exists.
177 * Creates new Accessor otherwise.
178 * @param optionsString The string of name=option pairs to define the Accessor (separated by ;).
179 * @return The Accessor associated to specified optionsString.
180 * @throws SQLTupleException if the Accessor can't be created. */
181 public static Accessor getAccessor(String optionsString) throws SQLTupleException {
182 return getAccessor(AidaUtils.parseOptions(optionsString));
183 }
184
185 /** Creates using informations from {@link java.util.Map}.
186 * @param optionsMap The map of options to define the Accessor.
187 * @throws SQLTupleException if the Accessor can't be created */
188 private Accessor(Map optionsMap) throws SQLTupleException {
189 _db = (String)optionsMap.get("hep.aida.ref.sql.db");
190 _src = (String)optionsMap.get("hep.aida.ref.sql.src");
191 _schema = (String)optionsMap.get("hep.aida.ref.sql.schema");
192 _user = (String)optionsMap.get("hep.aida.ref.sql.user");
193 _passwd = (String)optionsMap.get("hep.aida.ref.sql.passwd");
194 _protocol = (new AIDAURL(_db)).protocol();
195 String forwardOnly = (String)optionsMap.get("hep.aida.ref.sql.forwardonly");
196 _forwardOnly = "true".equals(forwardOnly) || supportsOnlyForwardOnly();
197 if (_src == null) {
198 _src = "hep/aida/ref/sql/StmtSrc.properties";
199 }
200 if (supportsOnlyUpperCase()) {
201 _user = _user.toUpperCase();
202 if (_schema != null) {
203 _schema = _schema.toUpperCase();
204 }
205 }
206 connection();
207 }
208
209 /** Create using informations from properties String.
210 * @param optionsString The string of name=option pairs to define the Accessor (separated by ;).
211 * @throws SQLTupleException if the Accessor can't be created. */
212 private Accessor(String optionsString) throws SQLTupleException {
213 this(AidaUtils.parseOptions(optionsString));
214 }
215
216 /** Gives {@link Connection}, creates one if it doesn't exist yet.
217 * @return The Connection defined by this Accessor.
218 * @throws SQLTupleException if the Connection can't be obtained. */
219 public Connection connection() throws SQLTupleException {
220 // Return Connection if it is already established
221 if (_connection != null) {
222 return _connection;
223 }
224 // Check for suitable driver
225 try {
226 Class.forName(driver());
227 }
228 catch (Exception e) {
229 throw new SQLTupleException("Unable to load SQL driver " + driver() + " for URL " + _db, e);
230 }
231 // Get Connection
232 try {
233 if (_connection == null) {
234 log.debug("Getting connection to database " + _db + creationCommand() + " as " + _user + "(" + _passwd + ")");
235 _connection = DriverManager.getConnection(_db + creationCommand(), _user, _passwd);
236 _connection.setAutoCommit(false);
237 _types = new Type(this);
238 }
239 }
240 catch (SQLException e) {
241 throw new SQLTupleException("Unable to connect to SQL database " + _db + creationCommand() + " as " + _user, e);
242 }
243 return _connection;
244 }
245
246 /** Give native SQL type name corresponding to Java Class.
247 * @param aidaType The Java Class representing AIDA type.
248 * @return The corresponding native SQL type name. */
249 public String sqlName(Class aidaType) {
250 return _types.sqlType(aidaType);
251 }
252
253 /** Commit.
254 * @throws SQLTupleException if the {@link Connection} can't be commited. */
255 public void commit() throws SQLTupleException {
256 log.debug("Commiting to database " + _db + " as " + _user + "(" + _passwd + ")");
257 try {
258 connection().commit();
259 }
260 catch (SQLException e) {
261 throw new SQLTupleException("Can't commit connection to database " + _db, e);
262 }
263 }
264
265 /** Roll back.
266 * @throws SQLTupleException if the {@link Connection} can't be rolledback. */
267 public void rollback() throws SQLTupleException {
268 log.debug("Rolling back to database " + _db + " as " + _user + "(" + _passwd + ")");
269 try {
270 connection().rollback();
271 }
272 catch (SQLException e) {
273 throw new SQLTupleException("Can't rollback connection to database " + _db, e);
274 }
275 }
276
277 /** Close connection.
278 * @throws SQLTupleException if the {@link Connection} can't be closed. */
279 public void close() throws SQLTupleException {
280 log.debug("Closing connection to database " + _db + " as " + _user + "(" + _passwd + ")");
281 if (_connection != null) {
282 try {
283 _connection.close();
284 }
285 catch (SQLException e) {
286 throw new SQLTupleException("Can't close connection to database " + _db, e);
287 }
288 finally {
289 _connection = null;
290 }
291 }
292 else {
293 log.debug("Can't close because Connection is unknown");
294 }
295 }
296
297 /** Check, if both {@link Accessor}s use the same
298 * <code>driver</code>, <code>database</code>, <code>schema</code> (if set),
299 * <code>user</code> and <code>src</code> */
300 public boolean equals(Object other) {
301 if (!(other instanceof Accessor)) {
302 return false;
303 }
304 Accessor a = (Accessor)other;
305 return toString().equals(a.toString());
306 }
307
308 /** Give used driver.
309 * @return The name of the associated SQL driver Class. */
310 public String driver() {
311 return Implementation.driver(_protocol);
312 }
313 /** Give the database creation command, if needed.
314 * @return The database creation command, if needed. */
315 public String creationCommand() {
316 return Implementation.creationCommand(_protocol);
317 }
318
319 /** Give used protocol.
320 * @return The name of the associated JDBC protocol. */
321 public String protocol() {
322 return _protocol;
323 }
324
325 /** Tell if database supports replication.
326 * @return Whether database supports replication. */
327 public boolean supportsReplication() {
328 return Implementation.supportsReplication(_protocol);
329 }
330
331 /** Tell if database supports only TYPE_FORWARD_ONLY cursor.
332 * @return Whether database supports only YPE_FORWARD_ONLY cursor. */
333 public boolean supportsOnlyForwardOnly() {
334 return Implementation.supportsOnlyForwardOnly(_protocol);
335 }
336
337 /** Tell if database supports only uppercase names..
338 * @return Whether database supports only uppercase names. */
339 public boolean supportsOnlyUpperCase() {
340 return Implementation.supportsOnlyUpperCase(_protocol);
341 }
342
343 /** Give connected database name.
344 * @return The name of the associated database. */
345 public String db() {
346 return _db;
347 }
348
349 /** Give connected database schema.
350 * @return The schema of the associated database. */
351 public String schema() {
352 return _schema;
353 }
354
355 /** Set connected <code>StmtSrc.properties</code>.
356 * @param src The StmtSrc to be used, <code>null</code>
357 * means the default StmtSrc. */
358 public void setSrc(String src) {
359 if (src == null) {
360 _src = "hep/aida/ref/sql/StmtSrc.properties";
361 }
362 else {
363 _src = src;
364 }
365 }
366
367 /** Give connected <code>StmtSrc.properties</code>.
368 * @return The used StmtSrc. */
369 public String src() {
370 return _src;
371 }
372
373 /** Give connected user.
374 * @return The used username. */
375 public String user() {
376 return _user;
377 }
378
379 /** Give connected user' password.
380 * @return The used password. */
381 public String passwd() {
382 return _passwd;
383 }
384
385 /** Tell if {@link java.sql.ResultSet} should be <em>FOWARD_ONLY</em>.
386 * @return Whether {@link java.sql.ResultSet} should be <em>FOWARD_ONLY</em>. */
387 public boolean forwardOnly() {
388 return _forwardOnly;
389 }
390
391 /** TBD */
392 public static void setWithinJAS() {
393 _withinJAS = true;
394 }
395
396 /** TBD */
397 public static boolean isWinhinJAS() {
398 return _withinJAS;
399 }
400
401 public String toString() {
402 return (_schema == null) ? "Accessor(" + _db + "[" + _user + "] using " + driver() + "[" + _src + "])"
403 : "Accessor(" + _db + "/" + _schema + "[" + _user + "] using " + driver() + "[" + _src + "])";
404 }
405
406 private static boolean _withinJAS = false;
407
408 private static Map<Map, Accessor> _accessors = new HashMap<Map, Accessor>();
409
410 private String _protocol;
411
412 private Connection _connection;
413
414 private Type _types;
415
416 private String _db;
417
418 private String _schema;
419
420 private String _src;
421
422 private String _user;
423
424 private String _passwd;
425
426 private boolean _forwardOnly;
427
428 /** Logging . */
429 private static Logger log = Logger.getLogger(Accessor.class);
430
431 }