1     /*
2    *  JDBCDataStore.java
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Marin Dimitrov, 18/Sep/2001
12   *
13   *  $Id: JDBCDataStore.java,v 1.54 2001/11/30 12:55:17 marin Exp $
14   */
15  
16  package gate.persist;
17  
18  import java.sql.*;
19  import java.net.*;
20  import java.util.*;
21  import java.io.*;
22  
23  import junit.framework.*;
24  
25  import gate.*;
26  import gate.util.*;
27  import gate.event.*;
28  import gate.security.*;
29  import gate.security.SecurityException;
30  
31  
32  public abstract class JDBCDataStore extends AbstractFeatureBearer
33                                      implements DatabaseDataStore,
34                                                  CreoleListener {
35  
36    /** --- */
37  /*  private static final String jdbcOracleDriverName = "oracle.jdbc.driver.OracleDriver";
38    private static final String jdbcPostgresDriverName = "postgresql.Driver";
39    private static final String jdbcSapDBDriverName = "com.sap.dbtech.jdbc.DriverSapDB";
40  */
41    private static final boolean DEBUG = false;
42  
43    /** --- */
44    private   String      dbURL;
45    private   String      driverName;
46    private   String      dbID;
47  
48    protected   Session           session;
49    protected   String            name;
50  
51    protected transient Connection  jdbcConn;
52    protected transient AccessController  ac;
53    private   transient Vector datastoreListeners;
54    protected transient Vector dependentResources;
55  
56    /** Do not use this class directly - use one of the subclasses */
57    protected JDBCDataStore() {
58  
59      this.datastoreListeners = new Vector();
60      this.dependentResources = new Vector();
61    }
62  
63  
64    /*  interface DataStore  */
65  
66    /**
67     * Returns the comment displayed by the GUI for this DataStore
68     */
69    public abstract String getComment();
70  
71    /**
72     * Returns the name of the icon to be used when this datastore is displayed
73     * in the GUI
74     */
75    public abstract String getIconName();
76  
77  
78    /** Get the name of an LR from its ID. */
79    public abstract String getLrName(Object lrId) throws PersistenceException;
80  
81  
82    /** Set the URL for the underlying storage mechanism. */
83    public void setStorageUrl(String storageUrl) throws PersistenceException {
84  
85      if (!storageUrl.startsWith("jdbc:")) {
86        throw new PersistenceException("Incorrect JDBC url (should start with \"jdbc:\")");
87      }
88      else {
89        this.dbURL = storageUrl;
90      }
91  
92    }
93  
94    /** Get the URL for the underlying storage mechanism. */
95    public String getStorageUrl() {
96  
97      return this.dbURL;
98    }
99  
100 
101   /**
102    * Create a new data store. <B>NOTE:</B> for some data stores
103    * creation is an system administrator task; in such cases this
104    * method will throw an UnsupportedOperationException.
105    */
106   public void create()
107   throws PersistenceException, UnsupportedOperationException {
108 
109     throw new UnsupportedOperationException("create() is not supported for DatabaseDataStore");
110   }
111 
112 
113 
114   /** Open a connection to the data store. */
115   public void open() throws PersistenceException {
116     try {
117 
118       //1, get connection to the DB
119       jdbcConn = DBHelper.connect(dbURL);
120 
121       //2. create security factory
122 //      this.ac = new AccessControllerImpl();
123       this.ac = Factory.createAccessController(dbURL);
124 
125       //3. open and init the security factory with the same DB repository
126       ac.open();
127 
128       //4. get DB ID
129       this.dbID = this.readDatabaseID();
130 
131     }
132     catch(SQLException sqle) {
133       throw new PersistenceException("could not get DB connection ["+ sqle.getMessage() +"]");
134     }
135     catch(ClassNotFoundException clse) {
136       throw new PersistenceException("cannot locate JDBC driver ["+ clse.getMessage() +"]");
137     }
138 
139     //5. register for Creole events
140     Gate.getCreoleRegister().addCreoleListener(this);
141   }
142 
143   /** Close the data store. */
144   public void close() throws PersistenceException {
145 
146     //-1. Unregister for Creole events
147     Gate.getCreoleRegister().removeCreoleListener(this);
148 
149     //0. sync all dependednt resources
150     for (int i=0; i< this.dependentResources.size(); i++) {
151       LanguageResource lr = (LanguageResource)this.dependentResources.elementAt(i);
152 
153       try {
154         sync(lr);
155       }
156       catch(SecurityException se) {
157         //do nothing
158         //there was an oper and modified resource for which the user has no write
159         //privileges
160         //not doing anything is perfectly ok because the resource won't bechanged in DB
161       }
162 
163       //unload UI component
164       Factory.deleteResource(lr);
165     }
166 
167     //1. close security factory
168       ac.close();
169 
170     //2. close the JDBC connection
171     try {
172       //rollback uncommited transactions
173       this.jdbcConn.rollback();
174       this.jdbcConn.close();
175     }
176     catch (SQLException sqle) {
177       throw new PersistenceException("cannot close JDBC connection, DB error is ["+
178                                       sqle.getMessage() +"]");
179     }
180 
181     //finally unregister this datastore from the GATE register of datastores
182     Gate.getDataStoreRegister().remove(this);
183   }
184 
185   /**
186    * Delete the data store. <B>NOTE:</B> for some data stores
187    * deletion is an system administrator task; in such cases this
188    * method will throw an UnsupportedOperationException.
189    */
190   public void delete()
191   throws PersistenceException, UnsupportedOperationException {
192 
193     throw new UnsupportedOperationException("delete() is not supported for DatabaseDataStore");
194   }
195 
196   /**
197    * Delete a resource from the data store.
198    * @param lrId a data-store specific unique identifier for the resource
199    * @param lrClassName class name of the type of resource
200    */
201   public abstract void delete(String lrClassName, Object lrId)
202     throws PersistenceException,SecurityException;
203 
204 
205   /**
206    * Save: synchonise the in-memory image of the LR with the persistent
207    * image.
208    */
209   public abstract void sync(LanguageResource lr)
210     throws PersistenceException,SecurityException;
211 
212 
213   /**
214    * Set method for the autosaving behaviour of the data store.
215    * <B>NOTE:</B> many types of datastore have no auto-save function,
216    * in which case this will throw an UnsupportedOperationException.
217    */
218   public void setAutoSaving(boolean autoSaving)
219   throws UnsupportedOperationException,PersistenceException {
220     try {
221       this.jdbcConn.setAutoCommit(true);
222     }
223     catch(SQLException sqle) {
224       throw new PersistenceException("cannot change autosave mode ["+sqle.getMessage()+"]");
225     }
226 
227   }
228 
229   /** Get the autosaving behaviour of the LR. */
230   public boolean isAutoSaving() {
231     throw new MethodNotImplementedException();
232   }
233 
234   /** Adopt a resource for persistence. */
235   public abstract LanguageResource adopt(LanguageResource lr,SecurityInfo secInfo)
236     throws PersistenceException,gate.security.SecurityException;
237 
238   /**
239    * Get a resource from the persistent store.
240    * <B>Don't use this method - use Factory.createResource with
241    * DataStore and DataStoreInstanceId parameters set instead.</B>
242    */
243   public abstract LanguageResource getLr(String lrClassName, Object lrPersistenceId)
244   throws PersistenceException,SecurityException;
245 
246   /** Get a list of the types of LR that are present in the data store. */
247   public abstract List getLrTypes() throws PersistenceException;
248 
249 
250   /** Get a list of the IDs of LRs of a particular type that are present. */
251   public abstract List getLrIds(String lrType) throws PersistenceException;
252 
253 
254   /** Get a list of the names of LRs of a particular type that are present. */
255   public abstract List getLrNames(String lrType) throws PersistenceException;
256 
257   /**
258    * Checks if the user (identified by the sessionID)
259    *  has read access to the LR
260    */
261   public abstract boolean canReadLR(Object lrID)
262     throws PersistenceException, gate.security.SecurityException;
263 
264 
265   /**
266    * Checks if the user (identified by the sessionID)
267    * has write access to the LR
268    */
269   public abstract boolean canWriteLR(Object lrID)
270     throws PersistenceException, gate.security.SecurityException;
271 
272 
273   /*  interface DatabaseDataStore  */
274 
275   /** --- */
276   public void beginTrans()
277     throws PersistenceException,UnsupportedOperationException{
278 
279     try {
280       this.jdbcConn.setAutoCommit(false);
281     }
282     catch(SQLException sqle) {
283       throw new PersistenceException("cannot begin transaction, DB error is: ["
284                                                       +sqle.getMessage()+"]");
285     }
286   }
287 
288 
289   /** --- */
290   public void commitTrans()
291     throws PersistenceException,UnsupportedOperationException{
292 
293     try {
294       this.jdbcConn.commit();
295     }
296     catch(SQLException sqle) {
297       throw new PersistenceException("cannot commit transaction, DB error is: ["
298                                                       +sqle.getMessage()+"]");
299     }
300 
301   }
302 
303   /** --- */
304   public void rollbackTrans()
305     throws PersistenceException,UnsupportedOperationException{
306 
307     try {
308       this.jdbcConn.rollback();
309     }
310     catch(SQLException sqle) {
311       throw new PersistenceException("cannot commit transaction, DB error is: ["
312                                                       +sqle.getMessage()+"]");
313     }
314 
315   }
316 
317   /** --- */
318   public Long timestamp()
319     throws PersistenceException{
320 
321     //implemented by the subclasses
322     throw new MethodNotImplementedException();
323   }
324 
325   /** --- */
326   public void deleteSince(Long timestamp)
327     throws PersistenceException{
328 
329     throw new MethodNotImplementedException();
330   }
331 
332   /** --- */
333   public void setDriver(String driverName)
334     throws PersistenceException{
335 
336     this.driverName = driverName;
337   }
338     /** Sets the name of this resource*/
339   public void setName(String name){
340     this.name = name;
341   }
342 
343   /** Returns the name of this resource*/
344   public String getName(){
345     return name;
346   }
347 
348 
349   /** --- */
350   protected int findFeatureType(Object value) {
351 
352     if (null == value)
353       return DBHelper.VALUE_TYPE_NULL;
354     else if (value instanceof Integer)
355       return DBHelper.VALUE_TYPE_INTEGER;
356     else if (value instanceof Long)
357       return DBHelper.VALUE_TYPE_LONG;
358     else if (value instanceof Boolean)
359       return DBHelper.VALUE_TYPE_BOOLEAN;
360     else if (value instanceof Double ||
361              value instanceof Float)
362       return DBHelper.VALUE_TYPE_FLOAT;
363     else if (value instanceof String)
364       return DBHelper.VALUE_TYPE_STRING;
365     else if (value instanceof List) {
366       //is the array empty?
367       List arr = (List)value;
368 
369       if (arr.isEmpty()) {
370         return DBHelper.VALUE_TYPE_EMPTY_ARR;
371       }
372       else {
373         Object element = arr.get(0);
374 
375         if (element  instanceof Integer)
376           return DBHelper.VALUE_TYPE_INTEGER_ARR;
377         else if (element  instanceof Long)
378           return DBHelper.VALUE_TYPE_LONG_ARR;
379         else if (element instanceof Boolean)
380           return DBHelper.VALUE_TYPE_BOOLEAN_ARR;
381         else if (element instanceof Double ||
382                  element instanceof Float)
383           return DBHelper.VALUE_TYPE_FLOAT_ARR;
384         else if (element instanceof String)
385           return DBHelper.VALUE_TYPE_STRING_ARR;
386       }
387     }
388     else if (value instanceof Serializable) {
389       return DBHelper.VALUE_TYPE_BINARY;
390     }
391 
392     //this should never happen
393     throw new IllegalArgumentException();
394   }
395 
396   /** --- */
397   public String getDatabaseID() {
398     return this.dbID;
399   }
400 
401   /** --- */
402   public abstract String readDatabaseID()
403     throws PersistenceException;
404 
405   /**
406    * Removes a a previously registered {@link gate.event.DatastoreListener}
407    * from the list listeners for this datastore
408    */
409   public void removeDatastoreListener(DatastoreListener l) {
410 //System.out.println("listener being removed...");
411     Assert.assertNotNull(this.datastoreListeners);
412 //    Assert.assertTrue(this.datastoreListeners.contains(l));
413 
414     Vector temp = (Vector)this.datastoreListeners.clone();
415     temp.remove(l);
416 
417     this.datastoreListeners = temp;
418   }
419 
420 
421   /**
422    * Registers a new {@link gate.event.DatastoreListener} with this datastore
423    */
424   public void addDatastoreListener(DatastoreListener l) {
425 //System.out.println("listener added...");
426     Assert.assertNotNull(this.datastoreListeners);
427     if (false == this.datastoreListeners.contains(l)) {
428       Vector temp = (Vector)this.datastoreListeners.clone();
429       temp.add(l);
430       this.datastoreListeners = temp;
431     }
432   }
433 
434   protected void fireResourceAdopted(DatastoreEvent e) {
435 
436     Assert.assertNotNull(datastoreListeners);
437     Vector temp = this.datastoreListeners;
438 
439     int count = temp.size();
440     for (int i = 0; i < count; i++) {
441       ((DatastoreListener)temp.elementAt(i)).resourceAdopted(e);
442     }
443   }
444 
445 
446   protected void fireResourceDeleted(DatastoreEvent e) {
447 
448     Assert.assertNotNull(datastoreListeners);
449     Vector temp = this.datastoreListeners;
450 
451     int count = temp.size();
452     for (int i = 0; i < count; i++) {
453 //System.out.println("notifying listener...");
454       ((DatastoreListener)temp.elementAt(i)).resourceDeleted(e);
455     }
456   }
457 
458 
459   protected void fireResourceWritten(DatastoreEvent e) {
460 //System.out.println("lrid=["+e.getResourceID()+"] written...");
461     Assert.assertNotNull(datastoreListeners);
462     Vector temp = this.datastoreListeners;
463 
464     int count = temp.size();
465     for (int i = 0; i < count; i++) {
466       ((DatastoreListener)temp.elementAt(i)).resourceWritten(e);
467     }
468   }
469 
470 
471   public void resourceLoaded(CreoleEvent e) {
472     if(DEBUG)
473       System.out.println("resource loaded...");
474   }
475 
476 
477   public void resourceUnloaded(CreoleEvent e) {
478 //Out.prln("RESOURCE UNLOADED res=["+e .getResource().getClass() .toString()+"], src=["+e.getSource().getClass().toString()+"]...");
479     Assert.assertNotNull(e.getResource());
480     if(! (e.getResource() instanceof LanguageResource))
481       return;
482 
483     //1. check it's our resource
484     LanguageResource lr = (LanguageResource)e.getResource();
485 //Out.prln("CLOSING A RESOURCE...");
486     //this is a resource from another DS, so no need to do anything
487     if(lr.getDataStore() != this)
488       return;
489 
490     //2. remove from the list of reosurce that should be sunced if DS is closed
491     this.dependentResources.remove(lr);
492 
493     //3. don't save it, this may not be the user's choice
494 
495     //4. remove the reource as listener for events from the DataStore
496     //otherwise the DS will continue sending it events when the reource is
497     // no longer active
498     this.removeDatastoreListener((DatastoreListener)lr);
499   }
500 
501   public void datastoreOpened(CreoleEvent e) {
502     if(DEBUG)
503       System.out.println("datastore opened...");
504   }
505 
506   public void datastoreCreated(CreoleEvent e) {
507     if(DEBUG)
508       System.out.println("datastore created...");
509   }
510 
511   public void datastoreClosed(CreoleEvent e) {
512     if(DEBUG)
513       System.out.println("datastore closed...");
514     //sync all dependent resources
515   }
516 
517   /** identify user using this datastore */
518   public void setSession(Session s)
519     throws gate.security.SecurityException {
520 
521     this.session = s;
522   }
523 
524 
525 
526   /** identify user using this datastore */
527   public Session getSession(Session s)
528     throws gate.security.SecurityException {
529 
530     return this.session;
531   }
532 
533 }
534