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