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      }