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.84 2003/01/07 17:02:14 valyt 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  import oracle.jdbc.driver.*;
25  
26  import gate.*;
27  import gate.util.*;
28  import gate.event.*;
29  import gate.security.*;
30  import gate.security.SecurityException;
31  import gate.corpora.*;
32  import gate.annotation.*;
33  
34  public abstract class JDBCDataStore extends AbstractFeatureBearer
35                                      implements DatabaseDataStore,
36                                                  CreoleListener {
37  
38    /** --- */
39    private static final boolean DEBUG = false;
40  
41    /** jdbc url for the database */
42    private   String      dbURL;
43    protected String      dbSchema;
44    protected int         dbType;
45  
46    protected String      datastoreComment;
47    protected String      iconName;
48  
49    /** jdbc driver name */
50  //  private   String      driverName;
51  
52    /**
53     *  GUID of the datastore
54     *  read from T_PARAMETER table
55     *  */
56    private   String      dbID;
57  
58    /** security session identifying all access to the datastore */
59    protected   Session           session;
60  
61    /** datastore name? */
62    protected   String            name;
63  
64    /** jdbc connection, all access to the database is made through this connection
65     */
66    protected transient Connection  jdbcConn;
67  
68    /** Security factory that contols access to objects in the datastore
69     *  the security session is from this factory
70     *  */
71    protected transient AccessController  ac;
72  
73    /** anyone interested in datastore related events */
74    private   transient Vector datastoreListeners;
75  
76    /** resources that should be sync-ed if datastore is close()-d */
77    protected transient Vector dependentResources;
78  
79    /** Do not use this class directly - use one of the subclasses */
80    protected JDBCDataStore() {
81  
82      this.datastoreListeners = new Vector();
83      this.dependentResources = new Vector();
84    }
85  
86  
87    /*  interface DataStore  */
88  
89    /**
90     * Save: synchonise the in-memory image of the LR with the persistent
91     * image.
92     */
93    public String getComment() {
94  
95      Assert.assertNotNull(this.datastoreComment);
96      return this.datastoreComment;
97    }
98  
99    /**
100    * Returns the name of the icon to be used when this datastore is displayed
101    * in the GUI
102    */
103   public String getIconName() {
104     Assert.assertNotNull(this.iconName);
105     return this.iconName;
106   }
107 
108 
109 
110   /** Get the name of an LR from its ID. */
111   public String getLrName(Object lrId)
112     throws PersistenceException {
113 
114     if (false == lrId instanceof Long) {
115       throw new IllegalArgumentException();
116     }
117 
118     Long ID = (Long)lrId;
119 
120     PreparedStatement pstmt = null;
121     ResultSet rset = null;
122 
123     try {
124       String sql = " select lr_name " +
125                   " from   "+this.dbSchema+"t_lang_resource " +
126                   " where  lr_id = ?";
127 
128       pstmt = this.jdbcConn.prepareStatement(sql);
129       pstmt.setLong(1,ID.longValue());
130       pstmt.execute();
131       rset = pstmt.getResultSet();
132 
133       rset.next();
134       String result = rset.getString("lr_name");
135 
136       return result;
137     }
138     catch(SQLException sqle) {
139       throw new PersistenceException("can't get LR name from DB: ["+ sqle.getMessage()+"]");
140     }
141     finally {
142       DBHelper.cleanup(pstmt);
143       DBHelper.cleanup(rset);
144     }
145   }
146 
147 
148 
149   /** Set the URL for the underlying storage mechanism. */
150   public void setStorageUrl(String storageUrl) throws PersistenceException {
151 
152     if (!storageUrl.startsWith("jdbc:")) {
153       throw new PersistenceException("Incorrect JDBC url (should start with \"jdbc:\")");
154     }
155     else {
156       this.dbURL = storageUrl;
157       this.dbSchema = DBHelper.getSchemaPrefix(this.dbURL);
158       this.dbType = DBHelper.getDatabaseType(this.dbURL);
159       Assert.assertNotNull(this.dbSchema);
160       Assert.assertTrue(this.dbType > 0);
161     }
162 
163   }
164 
165   /** Get the URL for the underlying storage mechanism. */
166   public String getStorageUrl() {
167 
168     return this.dbURL;
169   }
170 
171 
172   /**
173    * Create a new data store. <B>NOTE:</B> for some data stores
174    * creation is an system administrator task; in such cases this
175    * method will throw an UnsupportedOperationException.
176    */
177   public void create()
178   throws PersistenceException, UnsupportedOperationException {
179 
180     throw new UnsupportedOperationException("create() is not supported for DatabaseDataStore");
181   }
182 
183 
184 
185   /** Open a connection to the data store. */
186   public void open() throws PersistenceException {
187     try {
188 
189       //1, get connection to the DB
190       jdbcConn = DBHelper.connect(dbURL);
191 
192       //2. create security factory
193 //      this.ac = new AccessControllerImpl();
194       this.ac = Factory.createAccessController(dbURL);
195 
196       //3. open and init the security factory with the same DB repository
197       ac.open();
198 
199       //4. get DB ID
200       this.dbID = this.readDatabaseID();
201 
202     }
203     catch(SQLException sqle) {
204       throw new PersistenceException("could not get DB connection ["+ sqle.getMessage() +"]");
205     }
206     catch(ClassNotFoundException clse) {
207       throw new PersistenceException("cannot locate JDBC driver ["+ clse.getMessage() +"]");
208     }
209 
210     //5. register for Creole events
211     Gate.getCreoleRegister().addCreoleListener(this);
212   }
213 
214   /** Close the data store. */
215   public void close() throws PersistenceException {
216 
217     //-1. Unregister for Creole events
218     Gate.getCreoleRegister().removeCreoleListener(this);
219 
220     //0. sync all dependednt resources
221     for (int i=0; i< this.dependentResources.size(); i++) {
222       LanguageResource lr = (LanguageResource)this.dependentResources.elementAt(i);
223 
224       try {
225         sync(lr);
226       }
227       catch(SecurityException se) {
228         //do nothing
229         //there was an oper and modified resource for which the user has no write
230         //privileges
231         //not doing anything is perfectly ok because the resource won't bechanged in DB
232       }
233 
234       //unload UI component
235       Factory.deleteResource(lr);
236     }
237 
238     //1. close security factory
239     ac.close();
240 
241     DBHelper.disconnect(this.jdbcConn);
242 
243     //finally unregister this datastore from the GATE register of datastores
244     Gate.getDataStoreRegister().remove(this);
245   }
246 
247   /**
248    * Delete the data store. <B>NOTE:</B> for some data stores
249    * deletion is an system administrator task; in such cases this
250    * method will throw an UnsupportedOperationException.
251    */
252   public void delete()
253   throws PersistenceException, UnsupportedOperationException {
254 
255     throw new UnsupportedOperationException("delete() is not supported for DatabaseDataStore");
256   }
257 
258   /**
259    * Delete a resource from the data store.
260    * @param lrId a data-store specific unique identifier for the resource
261    * @param lrClassName class name of the type of resource
262    */
263 
264   public void delete(String lrClassName, Object lrId)
265   throws PersistenceException,SecurityException {
266     //0. preconditions
267     if (false == lrId instanceof Long) {
268       throw new IllegalArgumentException();
269     }
270 
271     if (!lrClassName.equals(DBHelper.DOCUMENT_CLASS) &&
272         !lrClassName.equals(DBHelper.CORPUS_CLASS)) {
273       throw new IllegalArgumentException("Only Corpus and Document classes are supported" +
274                                           " by Database data store");
275     }
276 
277     //1. check session
278     if (null == this.session) {
279       throw new SecurityException("session not set");
280     }
281 
282     if (false == this.ac.isValidSession(this.session)) {
283       throw new SecurityException("invalid session supplied");
284     }
285 
286     //2. check permissions
287     if (false == canWriteLR(lrId)) {
288       throw new SecurityException("insufficient privileges");
289     }
290 
291     //3. try to lock document, so that we'll be sure no one is editing it
292     //NOTE: use the private method
293     User lockingUser = this.getLockingUser((Long)lrId);
294     User currUser = this.session.getUser();
295 
296     if (null != lockingUser && false == lockingUser.equals(currUser)) {
297       //oops, someone is editing now
298       throw new PersistenceException("LR locked by another user");
299     }
300 
301     boolean transFailed = false;
302     try {
303       //4. autocommit should be FALSE because of LOBs
304       beginTrans();
305 
306       //5. perform changes, if anything goes wrong, rollback
307       if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
308         deleteDocument((Long)lrId);
309       }
310       else {
311         deleteCorpus((Long)lrId);
312       }
313 
314       //6. done, commit
315       commitTrans();
316     }
317     catch(PersistenceException pe) {
318       transFailed = true;
319       throw(pe);
320     }
321     finally {
322       //problems?
323       if (transFailed) {
324         rollbackTrans();
325       }
326     }
327 
328     //7, unlock
329     //do nothing - the resource does not exist anymore
330 
331     //8. delete from the list of dependent resources
332     boolean resourceFound = false;
333     Iterator it = this.dependentResources.iterator();
334     while (it.hasNext()) {
335       LanguageResource lr = (LanguageResource)it.next();
336       if (lr.getLRPersistenceId().equals(lrId)) {
337         resourceFound = true;
338         it.remove();
339         break;
340       }
341     }
342 
343     //Assert.assertTrue(resourceFound);
344 
345     //9. let the world know about it
346     fireResourceDeleted(
347       new DatastoreEvent(this, DatastoreEvent.RESOURCE_DELETED, null, lrId));
348 
349     //10. unload the resource form the GUI
350     try {
351       unloadLR((Long)lrId);
352     }
353     catch(GateException ge) {
354       Err.prln("can't unload resource from GUI...");
355     }
356   }
357 
358 
359 
360   /**
361    * Save: synchonise the in-memory image of the LR with the persistent
362    * image.
363    */
364   public void sync(LanguageResource lr)
365   throws PersistenceException,SecurityException {
366 
367     //4.delegate (open a new transaction)
368     _sync(lr,true);
369   }
370 
371 
372   /**
373    * Set method for the autosaving behaviour of the data store.
374    * <B>NOTE:</B> many types of datastore have no auto-save function,
375    * in which case this will throw an UnsupportedOperationException.
376    */
377   public void setAutoSaving(boolean autoSaving)
378   throws UnsupportedOperationException,PersistenceException {
379     try {
380       this.jdbcConn.setAutoCommit(true);
381     }
382     catch(SQLException sqle) {
383       throw new PersistenceException("cannot change autosave mode ["+sqle.getMessage()+"]");
384     }
385 
386   }
387 
388   /** Get the autosaving behaviour of the LR. */
389   public boolean isAutoSaving() {
390     throw new MethodNotImplementedException();
391   }
392 
393   /** Adopt a resource for persistence. */
394   public LanguageResource adopt(LanguageResource lr, SecurityInfo secInfo)
395   throws PersistenceException,SecurityException {
396     //open a new transaction
397     return _adopt(lr,secInfo,true);
398   }
399 
400 
401   protected LanguageResource _adopt(LanguageResource lr,
402                                   SecurityInfo secInfo,
403                                   boolean openNewTrans)
404   throws PersistenceException,SecurityException {
405 
406     LanguageResource result = null;
407 
408     //-1. preconditions
409     Assert.assertNotNull(lr);
410     Assert.assertNotNull(secInfo);
411     if (false == lr instanceof Document &&
412         false == lr instanceof Corpus) {
413       //only documents and corpuses could be serialized in DB
414       throw new IllegalArgumentException("only Documents and Corpuses could "+
415                                           "be serialized in DB");
416     }
417 
418     //0. check SecurityInfo
419     if (false == this.ac.isValidSecurityInfo(secInfo)) {
420       throw new SecurityException("Invalid security settings supplied");
421     }
422 
423     //1. user session should be set
424     if (null == this.session) {
425       throw new SecurityException("user session not set");
426     }
427 
428     //2. check the LR's current DS
429     DataStore currentDS = lr.getDataStore();
430     if(currentDS == null) {
431       // an orphan - do the adoption (later)
432     }
433     else if(currentDS.equals(this)){         // adopted already
434       return lr;
435     }
436     else {                      // someone else's child
437       throw new PersistenceException(
438         "Can't adopt a resource which is already in a different datastore");
439     }
440 
441 
442     //3. is the LR one of Document or Corpus?
443     if (false == lr instanceof Document &&
444         false == lr instanceof Corpus) {
445 
446       throw new IllegalArgumentException("Database datastore is implemented only for "+
447                                         "Documents and Corpora");
448     }
449 
450     //4.is the document already stored in this storage?
451     Object persistID = lr.getLRPersistenceId();
452     if (persistID != null) {
453       throw new PersistenceException("This LR is already stored in the " +
454                                       " database (persistance ID is =["+(Long)persistID+"] )");
455     }
456 
457     boolean transFailed = false;
458     try {
459       //5 autocommit should be FALSE because of LOBs
460       if (openNewTrans) {
461 //        this.jdbcConn.setAutoCommit(false);
462         beginTrans();
463       }
464 
465       //6. perform changes, if anything goes wrong, rollback
466       if (lr instanceof Document) {
467         result =  createDocument((Document)lr,secInfo);
468 //System.out.println("result ID=["+result.getLRPersistenceId()+"]");
469       }
470       else {
471         //adopt each document from the corpus in a separate transaction context
472         result =  createCorpus((Corpus)lr,secInfo,true);
473       }
474 
475       //7. done, commit
476       if (openNewTrans) {
477 //        this.jdbcConn.commit();
478         commitTrans();
479       }
480     }
481 /*
482     catch(SQLException sqle) {
483       transFailed = true;
484       throw new PersistenceException("Cannot start/commit a transaction, ["+sqle.getMessage()+"]");
485     }
486 */
487     catch(PersistenceException pe) {
488       transFailed = true;
489       throw(pe);
490     }
491     catch(SecurityException se) {
492       transFailed = true;
493       throw(se);
494     }
495     finally {
496       //problems?
497       if (transFailed) {
498 System.out.println("trans failed ...rollback");
499         rollbackTrans();
500 /*        try {
501           this.jdbcConn.rollback();
502         }
503         catch(SQLException sqle) {
504           throw new PersistenceException(sqle);
505         }
506 */
507       }
508     }
509 
510     //8. let the world know
511     fireResourceAdopted(
512         new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
513                            result,
514                            result.getLRPersistenceId())
515     );
516 
517     //9. fire also resource written event because it's now saved
518     fireResourceWritten(
519       new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
520                           result,
521                           result.getLRPersistenceId()
522       )
523     );
524 
525     //10. add the resource to the list of dependent resources - i.e. the ones that the
526     //data store should take care upon closing [and call sync()]
527     this.dependentResources.add(result);
528 
529     return result;
530   }
531 
532 
533   /** Get a list of the types of LR that are present in the data store. */
534   public List getLrTypes() throws PersistenceException {
535 
536     Vector lrTypes = new Vector();
537     Statement stmt = null;
538     ResultSet rs = null;
539 
540     try {
541       stmt = this.jdbcConn.createStatement();
542       rs = stmt.executeQuery(" SELECT lrtp_type " +
543                              " FROM   "+this.dbSchema+"t_lr_type LRTYPE ");
544 
545       while (rs.next()) {
546         //access by index is faster
547         String lrType = rs.getString(1);
548         lrTypes.add(lrType);
549       }
550 
551       return lrTypes;
552     }
553     catch(SQLException sqle) {
554       throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
555     }
556     finally {
557       DBHelper.cleanup(rs);
558       DBHelper.cleanup(stmt);
559     }
560   }
561 
562 
563   /** Get a list of the IDs of LRs of a particular type that are present. */
564   public List getLrIds(String lrType) throws PersistenceException {
565 
566     Vector lrIDs = new Vector();
567     PreparedStatement stmt = null;
568     ResultSet rs = null;
569 
570     try {
571       stmt = this.jdbcConn.prepareStatement(
572                       " SELECT lr_id " +
573                       " FROM   "+this.dbSchema+"t_lang_resource LR, " +
574                       "        "+this.dbSchema+"t_lr_type LRTYPE " +
575                       " WHERE  LR.lr_type_id = LRTYPE.lrtp_id " +
576                       "        AND LRTYPE.lrtp_type = ? " +
577                       " ORDER BY lr_name"
578                       );
579       stmt.setString(1,lrType);
580 
581       //oracle special
582       if (this.dbType == DBHelper.ORACLE_DB) {
583         ((OraclePreparedStatement)stmt).setRowPrefetch(DBHelper.CHINK_SIZE_SMALL);
584       }
585 
586       stmt.execute();
587       rs = stmt.getResultSet();
588 
589       while (rs.next()) {
590         //access by index is faster
591         Long lrID = new Long(rs.getLong(1));
592         lrIDs.add(lrID);
593       }
594 
595       return lrIDs;
596     }
597     catch(SQLException sqle) {
598       throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
599     }
600     finally {
601       DBHelper.cleanup(rs);
602       DBHelper.cleanup(stmt);
603     }
604 
605   }
606 
607 
608   /** Get a list of the names of LRs of a particular type that are present. */
609   public List getLrNames(String lrType) throws PersistenceException {
610 
611     Vector lrNames = new Vector();
612     PreparedStatement stmt = null;
613     ResultSet rs = null;
614 
615     try {
616       stmt = this.jdbcConn.prepareStatement(
617                 " SELECT lr_name " +
618                 " FROM   "+this.dbSchema+"t_lang_resource LR, " +
619                 "        t_lr_type LRTYPE " +
620                 " WHERE  LR.lr_type_id = LRTYPE.lrtp_id " +
621                 "        AND LRTYPE.lrtp_type = ? " +
622                 " ORDER BY lr_name desc"
623                 );
624       stmt.setString(1,lrType);
625 
626       //Oracle special
627       if (this.dbType == DBHelper.ORACLE_DB) {
628         ((OraclePreparedStatement)stmt).setRowPrefetch(DBHelper.CHINK_SIZE_SMALL);
629       }
630 
631       stmt.execute();
632       rs = stmt.getResultSet();
633 
634       while (rs.next()) {
635         //access by index is faster
636         String lrName = rs.getString(1);
637         lrNames.add(lrName);
638       }
639 
640       return lrNames;
641     }
642     catch(SQLException sqle) {
643       throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
644     }
645     finally {
646       DBHelper.cleanup(rs);
647       DBHelper.cleanup(stmt);
648     }
649   }
650 
651   /**
652    * Checks if the user (identified by the sessionID)
653    *  has read access to the LR
654    */
655   public boolean canReadLR(Object lrID)
656     throws PersistenceException, SecurityException{
657 
658     return canAccessLR((Long) lrID,DBHelper.READ_ACCESS);
659   }
660 
661 
662 
663   /**
664    * Checks if the user (identified by the sessionID)
665    * has write access to the LR
666    */
667   public boolean canWriteLR(Object lrID)
668     throws PersistenceException, SecurityException{
669 
670     return canAccessLR((Long) lrID,DBHelper.WRITE_ACCESS);
671   }
672 
673   /**
674    * Checks if the user (identified by the sessionID)
675    * has some access (read/write) to the LR
676    */
677   protected boolean canAccessLR(Long lrID,int mode)
678     throws PersistenceException, SecurityException{
679 
680     //abstract
681     throw new MethodNotImplementedException();
682   }
683 
684   /*  interface DatabaseDataStore  */
685 
686   /**
687    * starts a transaction
688    * note that if u're already in transaction context this will not open
689    * nested transaction
690    * i.e. many consecutive calls to beginTrans() make no difference if no commit/rollback
691    * is made meanwhile
692    *  */
693   public void beginTrans()
694     throws PersistenceException,UnsupportedOperationException{
695 
696     try {
697       this.jdbcConn.setAutoCommit(false);
698     }
699     catch(SQLException sqle) {
700       throw new PersistenceException("cannot begin transaction, DB error is: ["
701                                                       +sqle.getMessage()+"]");
702     }
703   }
704 
705 
706   /**
707    * commits transaction
708    * note that this will commit all the uncommited calls made so far
709    *  */
710   public void commitTrans()
711     throws PersistenceException,UnsupportedOperationException{
712 
713     try {
714       this.jdbcConn.commit();
715     }
716     catch(SQLException sqle) {
717       throw new PersistenceException("cannot commit transaction, DB error is: ["
718                                                       +sqle.getMessage()+"]");
719     }
720 
721   }
722 
723   /** rollsback a transaction */
724   public void rollbackTrans()
725     throws PersistenceException,UnsupportedOperationException{
726 
727     try {
728       this.jdbcConn.rollback();
729     }
730     catch(SQLException sqle) {
731       throw new PersistenceException("cannot commit transaction, DB error is: ["
732                                                       +sqle.getMessage()+"]");
733     }
734 
735   }
736 
737   /** not used */
738   public Long timestamp()
739     throws PersistenceException{
740 
741     //implemented by the subclasses
742     throw new MethodNotImplementedException();
743   }
744 
745   /** not used */
746   public void deleteSince(Long timestamp)
747     throws PersistenceException{
748 
749     throw new MethodNotImplementedException();
750   }
751 
752   /** specifies the driver to be used to connect to the database? */
753 /*  public void setDriver(String driverName)
754     throws PersistenceException{
755 
756     this.driverName = driverName;
757   }
758 */
759   /** Sets the name of this resource*/
760   public void setName(String name){
761     this.name = name;
762   }
763 
764   /** Returns the name of this resource*/
765   public String getName(){
766     return name;
767   }
768 
769 
770   /** --- */
771   protected int findFeatureType(Object value) {
772 
773     if (null == value)
774       return DBHelper.VALUE_TYPE_NULL;
775     else if (value instanceof Integer)
776       return DBHelper.VALUE_TYPE_INTEGER;
777     else if (value instanceof Long)
778       return DBHelper.VALUE_TYPE_LONG;
779     else if (value instanceof Boolean)
780       return DBHelper.VALUE_TYPE_BOOLEAN;
781     else if (value instanceof Double ||
782              value instanceof Float)
783       return DBHelper.VALUE_TYPE_FLOAT;
784     else if (value instanceof String)
785       return DBHelper.VALUE_TYPE_STRING;
786     else if (value instanceof List) {
787       //is the array empty?
788       List arr = (List)value;
789 
790       if (arr.isEmpty()) {
791         return DBHelper.VALUE_TYPE_EMPTY_ARR;
792       }
793       else {
794         Object element = arr.get(0);
795 
796         if (element  instanceof Integer)
797           return DBHelper.VALUE_TYPE_INTEGER_ARR;
798         else if (element  instanceof Long)
799           return DBHelper.VALUE_TYPE_LONG_ARR;
800         else if (element instanceof Boolean)
801           return DBHelper.VALUE_TYPE_BOOLEAN_ARR;
802         else if (element instanceof Double ||
803                  element instanceof Float)
804           return DBHelper.VALUE_TYPE_FLOAT_ARR;
805         else if (element instanceof String)
806           return DBHelper.VALUE_TYPE_STRING_ARR;
807       }
808     }
809     else if (value instanceof Serializable) {
810       return DBHelper.VALUE_TYPE_BINARY;
811     }
812 
813     //this should never happen
814     throw new IllegalArgumentException();
815   }
816 
817   /** --- */
818   public String getDatabaseID() {
819     return this.dbID;
820   }
821 
822   /** reads the GUID from the database */
823 /*  protected abstract String readDatabaseID()
824     throws PersistenceException;
825 */
826   /**
827    *  reads the ID of the database
828    *  every database should have unique string ID
829    */
830   protected String readDatabaseID() throws PersistenceException{
831 
832     PreparedStatement pstmt = null;
833     ResultSet rs = null;
834     String  result = null;
835 
836     //1. read from DB
837     try {
838       String sql = " select par_value_string " +
839                    " from  "+this.dbSchema+"t_parameter " +
840                    " where  par_key = ? ";
841 
842       pstmt = this.jdbcConn.prepareStatement(sql);
843       pstmt.setString(1,DBHelper.DB_PARAMETER_GUID);
844       pstmt.execute();
845       rs = pstmt.getResultSet();
846 
847       if (false == rs.next()) {
848         throw new PersistenceException("Can't read database parameter ["+
849                                           DBHelper.DB_PARAMETER_GUID+"]");
850       }
851       result = rs.getString(1);
852     }
853     catch(SQLException sqle) {
854         throw new PersistenceException("Can't read database parameter ["+
855                                           sqle.getMessage()+"]");
856     }
857     finally {
858       DBHelper.cleanup(rs);
859       DBHelper.cleanup(pstmt);
860     }
861 
862     if (DEBUG) {
863       Out.println("reult=["+result+"]");
864     }
865 
866     return result;
867   }
868 
869 
870   /**
871    * Removes a a previously registered {@link gate.event.DatastoreListener}
872    * from the list listeners for this datastore
873    */
874   public void removeDatastoreListener(DatastoreListener l) {
875 
876     Assert.assertNotNull(this.datastoreListeners);
877 
878     synchronized(this.datastoreListeners) {
879       this.datastoreListeners.remove(l);
880     }
881   }
882 
883 
884   /**
885    * Registers a new {@link gate.event.DatastoreListener} with this datastore
886    */
887   public void addDatastoreListener(DatastoreListener l) {
888 
889     Assert.assertNotNull(this.datastoreListeners);
890 
891     //this is not thread safe
892 /*    if (false == this.datastoreListeners.contains(l)) {
893       Vector temp = (Vector)this.datastoreListeners.clone();
894       temp.add(l);
895       this.datastoreListeners = temp;
896     }
897 */
898     synchronized(this.datastoreListeners) {
899       if (false == this.datastoreListeners.contains(l)) {
900         this.datastoreListeners.add(l);
901       }
902     }
903   }
904 
905   protected void fireResourceAdopted(DatastoreEvent e) {
906 
907     Assert.assertNotNull(datastoreListeners);
908     Vector temp = this.datastoreListeners;
909 
910     int count = temp.size();
911     for (int i = 0; i < count; i++) {
912       ((DatastoreListener)temp.elementAt(i)).resourceAdopted(e);
913     }
914   }
915 
916 
917   protected void fireResourceDeleted(DatastoreEvent e) {
918 
919     Assert.assertNotNull(datastoreListeners);
920     Vector temp = this.datastoreListeners;
921 
922     int count = temp.size();
923     for (int i = 0; i < count; i++) {
924       ((DatastoreListener)temp.elementAt(i)).resourceDeleted(e);
925     }
926   }
927 
928 
929   protected void fireResourceWritten(DatastoreEvent e) {
930     Assert.assertNotNull(datastoreListeners);
931     Vector temp = this.datastoreListeners;
932 
933     int count = temp.size();
934     for (int i = 0; i < count; i++) {
935       ((DatastoreListener)temp.elementAt(i)).resourceWritten(e);
936     }
937   }
938 
939   public void resourceLoaded(CreoleEvent e) {
940     if(DEBUG)
941       System.out.println("resource loaded...");
942   }
943 
944   public void resourceRenamed(Resource resource, String oldName,
945                               String newName){
946   }
947 
948 
949   public void resourceUnloaded(CreoleEvent e) {
950 
951     Assert.assertNotNull(e.getResource());
952     if(! (e.getResource() instanceof LanguageResource))
953       return;
954 
955     //1. check it's our resource
956     LanguageResource lr = (LanguageResource)e.getResource();
957 
958     //this is a resource from another DS, so no need to do anything
959     if(lr.getDataStore() != this)
960       return;
961 
962     //2. remove from the list of reosurce that should be sunced if DS is closed
963     this.dependentResources.remove(lr);
964 
965     //3. don't save it, this may not be the user's choice
966 
967     //4. remove the reource as listener for events from the DataStore
968     //otherwise the DS will continue sending it events when the reource is
969     // no longer active
970     this.removeDatastoreListener((DatastoreListener)lr);
971   }
972 
973   public void datastoreOpened(CreoleEvent e) {
974     if(DEBUG)
975       System.out.println("datastore opened...");
976   }
977 
978   public void datastoreCreated(CreoleEvent e) {
979     if(DEBUG)
980       System.out.println("datastore created...");
981   }
982 
983   public void datastoreClosed(CreoleEvent e) {
984     if(DEBUG)
985       System.out.println("datastore closed...");
986     //sync all dependent resources
987   }
988 
989   /** identify user using this datastore */
990   public void setSession(Session s)
991     throws gate.security.SecurityException {
992 
993     this.session = s;
994   }
995 
996 
997 
998   /** identify user using this datastore */
999   public Session getSession(Session s)
1000    throws gate.security.SecurityException {
1001
1002    return this.session;
1003  }
1004
1005  /** Get a list of LRs that satisfy some set or restrictions */
1006  public abstract List findLrIds(List constraints) throws PersistenceException;
1007
1008  /**
1009   *  Get a list of LRs that satisfy some set or restrictions and are
1010   *  of a particular type
1011   */
1012  public abstract List findLrIds(List constraints, String lrType)
1013  throws PersistenceException;
1014
1015
1016  /** get security information for LR . */
1017  public SecurityInfo getSecurityInfo(LanguageResource lr)
1018    throws PersistenceException {
1019
1020    //0. preconditions
1021    Assert.assertNotNull(lr);
1022    Assert.assertNotNull(lr.getLRPersistenceId());
1023    Assert.assertTrue(lr.getLRPersistenceId() instanceof Long);
1024    Assert.assertEquals(this,lr.getDataStore());
1025    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
1026                      lr instanceof DatabaseCorpusImpl);
1027
1028    PreparedStatement pstmt = null;
1029    ResultSet rs = null;
1030
1031    //1. read data
1032    Long userID = null;
1033    Long groupID = null;
1034    int  perm;
1035    try {
1036      String sql =  "   select lr_owner_user_id, "+
1037                    "          lr_owner_group_id, " +
1038                    "          lr_access_mode "+
1039                    "   from   "+this.dbSchema+"t_lang_resource "+
1040                    "   where  lr_id = ?";
1041      pstmt = this.jdbcConn.prepareStatement(sql);
1042      pstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
1043      rs = pstmt.executeQuery();
1044
1045      if (false == rs.next()) {
1046        throw new PersistenceException("Invalid LR ID supplied - no data found");
1047      }
1048
1049      userID = new Long(rs.getLong("lr_owner_user_id"));
1050      groupID = new Long(rs.getLong("lr_owner_group_id"));
1051      perm = rs.getInt("lr_access_mode");
1052
1053      Assert.assertTrue(perm == SecurityInfo.ACCESS_GR_GW ||
1054                        perm == SecurityInfo.ACCESS_GR_OW ||
1055                        perm == SecurityInfo.ACCESS_OR_OW ||
1056                        perm == SecurityInfo.ACCESS_WR_GW);
1057    }
1058    catch(SQLException sqle) {
1059      throw new PersistenceException("Can't read document permissions from DB, error is [" +
1060                                      sqle.getMessage() +"]");
1061    }
1062    finally {
1063      DBHelper.cleanup(rs);
1064      DBHelper.cleanup(pstmt);
1065    }
1066
1067    //2. get data from AccessController
1068    User usr = null;
1069    Group grp = null;
1070    try {
1071      usr = this.ac.findUser(userID);
1072      grp = this.ac.findGroup(groupID);
1073    }
1074    catch (SecurityException se) {
1075      throw new PersistenceException("Invalid security settings found in DB [" +
1076                                      se.getMessage() +"]");
1077    }
1078
1079    //3. construct SecurityInfo
1080    SecurityInfo si = new SecurityInfo(perm,usr,grp);
1081
1082
1083    return si;
1084  }
1085
1086  /** creates a LR of type Corpus  */
1087  protected Corpus createCorpus(Corpus corp,SecurityInfo secInfo, boolean newTransPerDocument)
1088    throws PersistenceException,SecurityException {
1089
1090    //1. create an LR entry for the corpus (T_LANG_RESOURCE table)
1091    Long lrID = createLR(DBHelper.CORPUS_CLASS,corp.getName(),secInfo,null);
1092
1093    //2.create am entry in the T_COPRUS table
1094    Long corpusID = null;
1095    //DB stuff
1096    CallableStatement cstmt = null;
1097    PreparedStatement pstmt = null;
1098    ResultSet rs = null;
1099
1100    try {
1101      if (this.dbType == DBHelper.ORACLE_DB) {
1102        cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.create_corpus(?,?) }");
1103        cstmt.setLong(1,lrID.longValue());
1104        cstmt.registerOutParameter(2,java.sql.Types.BIGINT);
1105        cstmt.execute();
1106        corpusID = new Long(cstmt.getLong(2));
1107      }
1108      else if (this.dbType == DBHelper.POSTGRES_DB) {
1109        pstmt = this.jdbcConn.prepareStatement("select persist_create_corpus(?) ");
1110        pstmt.setLong(1,lrID.longValue());
1111        pstmt.execute();
1112        rs = pstmt.getResultSet();
1113
1114        if (false == rs.next()) {
1115          throw new PersistenceException("empty result set");
1116        }
1117
1118        corpusID = new Long(rs.getLong(1));
1119      }
1120      else {
1121        Assert.fail();
1122      }
1123    }
1124    catch(SQLException sqle) {
1125      throw new PersistenceException("can't create corpus [step 2] in DB: ["+ sqle.getMessage()+"]");
1126    }
1127    finally {
1128      DBHelper.cleanup(cstmt);
1129      DBHelper.cleanup(pstmt);
1130      DBHelper.cleanup(rs);
1131    }
1132
1133    //3. for each document in the corpus call createDocument()
1134    Iterator itDocuments = corp.iterator();
1135    Vector dbDocs = new Vector();
1136    while (itDocuments.hasNext()) {
1137      Document doc = (Document)itDocuments.next();
1138
1139      //3.1. ensure that the document is either transient or is from the ...
1140      // same DataStore
1141      if (doc.getLRPersistenceId() == null) {
1142        //transient document
1143
1144        //now this is a bit ugly patch, the transaction related functionality
1145        //should not be in this method
1146        if (newTransPerDocument) {
1147          beginTrans();
1148        }
1149
1150        Document dbDoc = createDocument(doc,corpusID,secInfo);
1151
1152        if (newTransPerDocument) {
1153          commitTrans();
1154        }
1155
1156        dbDocs.add(dbDoc);
1157        //8. let the world know
1158        fireResourceAdopted(new DatastoreEvent(this,
1159                                                DatastoreEvent.RESOURCE_ADOPTED,
1160                                                dbDoc,
1161                                                dbDoc.getLRPersistenceId()
1162                                              )
1163                            );
1164
1165        //9. fire also resource written event because it's now saved
1166        fireResourceWritten(new DatastoreEvent(this,
1167                                                DatastoreEvent.RESOURCE_WRITTEN,
1168                                                dbDoc,
1169                                                dbDoc.getLRPersistenceId()
1170                                              )
1171                           );
1172
1173      }
1174      else if (doc.getDataStore().equals(this)) {
1175        //persistent doc from the same DataStore
1176        fireResourceAdopted(
1177            new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
1178                               doc,
1179                               doc.getLRPersistenceId()));
1180
1181        //6. fire also resource written event because it's now saved
1182        fireResourceWritten(
1183          new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
1184                              doc,
1185                              doc.getLRPersistenceId()));
1186      }
1187      else {
1188        //persistent doc from other datastore
1189        //skip
1190        gate.util.Err.prln("document ["+doc.getLRPersistenceId()+"] is adopted from another "+
1191                            " datastore. Skipped.");
1192      }
1193    }
1194
1195    //4. create features
1196    if (this.dbType == DBHelper.ORACLE_DB) {
1197      createFeaturesBulk(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1198    }
1199    else if (this.dbType == DBHelper.POSTGRES_DB) {
1200      createFeatures(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1201    }
1202    else {
1203      Assert.fail();
1204    }
1205
1206
1207    //5. create a DatabaseCorpusImpl and return it
1208///    Corpus dbCorpus = new DatabaseCorpusImpl(corp.getName(),
1209///                                             this,
1210///                                              lrID,
1211///                                              corp.getFeatures(),
1212///                                              dbDocs);
1213///
1214
1215    Corpus dbCorpus = null;
1216    FeatureMap params = Factory.newFeatureMap();
1217    HashMap initData = new HashMap();
1218
1219    initData.put("DS",this);
1220    initData.put("LR_ID",lrID);
1221    initData.put("CORP_NAME",corp.getName());
1222    initData.put("CORP_FEATURES",corp.getFeatures());
1223    initData.put("CORP_SUPPORT_LIST",dbDocs);
1224
1225    params.put("initData__$$__", initData);
1226
1227    try {
1228      //here we create the persistent LR via Factory, so it's registered
1229      //in GATE
1230      dbCorpus = (Corpus)Factory.createResource("gate.corpora.DatabaseCorpusImpl", params);
1231    }
1232    catch (gate.creole.ResourceInstantiationException ex) {
1233      throw new GateRuntimeException(ex.getMessage());
1234    }
1235
1236    //6. done
1237    return dbCorpus;
1238  }
1239
1240  /**
1241   * helper for adopt
1242   * creates a LR of type Document
1243   */
1244  protected Document createDocument(Document doc,SecurityInfo secInfo)
1245  throws PersistenceException,SecurityException {
1246
1247    //delegate, set to Null
1248    return createDocument(doc,null,secInfo);
1249  }
1250
1251
1252  /**
1253   * helper for adopt
1254   * creates a LR of type Document
1255   */
1256  protected Document createDocument(Document doc, Long corpusID,SecurityInfo secInfo)
1257  throws PersistenceException,SecurityException {
1258
1259    //-1. preconditions
1260    Assert.assertNotNull(doc);
1261    Assert.assertNotNull(secInfo);
1262
1263    //0. check securoity settings
1264    if (false == this.ac.isValidSecurityInfo(secInfo)) {
1265      throw new SecurityException("Invalid security settings");
1266    }
1267
1268    //1. get the data to be stored
1269    AnnotationSet defaultAnnotations = doc.getAnnotations();
1270    DocumentContent docContent = doc.getContent();
1271    FeatureMap docFeatures = doc.getFeatures();
1272    String docName  = doc.getName();
1273    URL docURL = doc.getSourceUrl();
1274    Boolean docIsMarkupAware = doc.getMarkupAware();
1275    Long docStartOffset = doc.getSourceUrlStartOffset();
1276    Long docEndOffset = doc.getSourceUrlEndOffset();
1277    String docEncoding = null;
1278    try {
1279      docEncoding = (String)doc.
1280        getParameterValue(Document.DOCUMENT_ENCODING_PARAMETER_NAME);
1281    }
1282    catch(gate.creole.ResourceInstantiationException re) {
1283      throw new PersistenceException("cannot create document: error getting " +
1284                                     " document encoding ["+re.getMessage()+"]");
1285    }
1286
1287
1288    //3. create a Language Resource (an entry in T_LANG_RESOURCE) for this document
1289    Long lrID = createLR(DBHelper.DOCUMENT_CLASS,docName,secInfo,null);
1290
1291    //4. create a record in T_DOCUMENT for this document
1292    Long docID = createDoc(lrID,
1293                            docURL,
1294                            docEncoding,
1295                            docStartOffset,
1296                            docEndOffset,
1297                            docIsMarkupAware,
1298                            corpusID);
1299
1300
1301    //5. fill document content (record[s] in T_DOC_CONTENT)
1302
1303    //do we have content at all?
1304    if (docContent.size().longValue() > 0) {
1305//      updateDocumentContent(docContentID,docContent);
1306      updateDocumentContent(docID,docContent);
1307    }
1308
1309    //6. insert annotations, etc
1310
1311    //6.1. create default annotation set
1312    createAnnotationSet(lrID,defaultAnnotations);
1313
1314    //6.2. create named annotation sets
1315    Map namedAnns = doc.getNamedAnnotationSets();
1316    //the map may be null
1317    if (null != namedAnns) {
1318      Set setAnns = namedAnns.entrySet();
1319      Iterator itAnns = setAnns.iterator();
1320
1321      while (itAnns.hasNext()) {
1322        Map.Entry mapEntry = (Map.Entry)itAnns.next();
1323        //String currAnnName = (String)mapEntry.getKey();
1324        AnnotationSet currAnnSet = (AnnotationSet)mapEntry.getValue();
1325
1326        //create a-sets
1327        createAnnotationSet(lrID,currAnnSet);
1328      }
1329    }
1330
1331    //7. create features
1332    if (this.dbType == DBHelper.ORACLE_DB) {
1333      createFeaturesBulk(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
1334    }
1335    else if (this.dbType == DBHelper.POSTGRES_DB) {
1336      createFeatures(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
1337    }
1338    else {
1339      Assert.fail();
1340    }
1341
1342
1343    //9. create a DatabaseDocument wrapper and return it
1344
1345/*    Document dbDoc = new DatabaseDocumentImpl(this.jdbcConn,
1346                                              doc.getName(),
1347                                              this,
1348                                              lrID,
1349                                              doc.getContent(),
1350                                              doc.getFeatures(),
1351                                              doc.getMarkupAware(),
1352                                              doc.getSourceUrl(),
1353                                              doc.getSourceUrlStartOffset(),
1354                                              doc.getSourceUrlEndOffset(),
1355                                              doc.getAnnotations(),
1356                                              doc.getNamedAnnotationSets());
1357*/
1358    Document dbDoc = null;
1359    FeatureMap params = Factory.newFeatureMap();
1360
1361    HashMap initData = new HashMap();
1362    initData.put("JDBC_CONN",this.jdbcConn);
1363    initData.put("DS",this);
1364    initData.put("LR_ID",lrID);
1365    initData.put("DOC_NAME",doc.getName());
1366    initData.put("DOC_CONTENT",doc.getContent());
1367    initData.put("DOC_FEATURES",doc.getFeatures());
1368    initData.put("DOC_MARKUP_AWARE",doc.getMarkupAware());
1369    initData.put("DOC_SOURCE_URL",doc.getSourceUrl());
1370    if(doc instanceof DocumentImpl){
1371      initData.put("DOC_STRING_CONTENT",
1372                   ((DocumentImpl)doc).getStringContent());
1373    }
1374    initData.put("DOC_SOURCE_URL_START",doc.getSourceUrlStartOffset());
1375    initData.put("DOC_SOURCE_URL_END",doc.getSourceUrlEndOffset());
1376    initData.put("DOC_DEFAULT_ANNOTATIONS",doc.getAnnotations());
1377    initData.put("DOC_NAMED_ANNOTATION_SETS",doc.getNamedAnnotationSets());
1378
1379    params.put("initData__$$__", initData);
1380
1381    try {
1382      //here we create the persistent LR via Factory, so it's registered
1383      //in GATE
1384      dbDoc = (Document)Factory.createResource("gate.corpora.DatabaseDocumentImpl", params);
1385    }
1386    catch (gate.creole.ResourceInstantiationException ex) {
1387      throw new GateRuntimeException(ex.getMessage());
1388    }
1389
1390    return dbDoc;
1391  }
1392
1393  protected abstract Long createLR(String lrType,
1394                          String lrName,
1395                          SecurityInfo si,
1396                          Long lrParentID)
1397    throws PersistenceException,SecurityException;
1398
1399
1400  protected abstract Long createDoc(Long _lrID,
1401                          URL _docURL,
1402                          String _docEncoding,
1403                          Long _docStartOffset,
1404                          Long _docEndOffset,
1405                          Boolean _docIsMarkupAware,
1406                          Long _corpusID)
1407    throws PersistenceException;
1408
1409  protected abstract void updateDocumentContent(Long docID,DocumentContent content)
1410    throws PersistenceException;
1411
1412  protected abstract void createAnnotationSet(Long lrID, AnnotationSet aset)
1413    throws PersistenceException;
1414
1415  protected abstract void createFeaturesBulk(Long entityID, int entityType, FeatureMap features)
1416    throws PersistenceException;
1417
1418  protected abstract void createFeatures(Long entityID, int entityType, FeatureMap features)
1419    throws PersistenceException;
1420
1421  /**
1422   * Save: synchonise the in-memory image of the LR with the persistent
1423   * image.
1424   */
1425  protected void _sync(LanguageResource lr, boolean openNewTrans)
1426    throws PersistenceException,SecurityException {
1427
1428    //0.preconditions
1429    Assert.assertNotNull(lr);
1430    Long lrID = (Long)lr.getLRPersistenceId();
1431
1432    if (false == lr instanceof Document &&
1433        false == lr instanceof Corpus) {
1434      //only documents and corpuses could be serialized in DB
1435      throw new IllegalArgumentException("only Documents and Corpuses could "+
1436                                          "be serialized in DB");
1437    }
1438
1439    // check that this LR is one of ours (i.e. has been adopted)
1440    if( null == lr.getDataStore() || false == lr.getDataStore().equals(this))
1441      throw new PersistenceException(
1442        "This LR is not stored in this DataStore"
1443      );
1444
1445
1446    //1. check session
1447    if (null == this.session) {
1448      throw new SecurityException("session not set");
1449    }
1450
1451    if (false == this.ac.isValidSession(this.session)) {
1452      throw new SecurityException("invalid session supplied");
1453    }
1454
1455    //2. check permissions
1456    if (false == canWriteLR(lrID)) {
1457      throw new SecurityException("insufficient privileges");
1458    }
1459
1460    //3. is the resource locked?
1461    User lockingUser = getLockingUser(lr);
1462    User currUser = this.session.getUser();
1463
1464    if (lockingUser != null && false == lockingUser.equals(currUser)) {
1465      throw new PersistenceException("document is locked by another user and cannot be synced");
1466    }
1467
1468
1469    boolean transFailed = false;
1470    try {
1471      //2. autocommit should be FALSE because of LOBs
1472      if (openNewTrans) {
1473        beginTrans();
1474      }
1475
1476      //3. perform changes, if anything goes wrong, rollback
1477      if (lr instanceof Document) {
1478        syncDocument((Document)lr);
1479      }
1480      else {
1481        syncCorpus((Corpus)lr);
1482      }
1483
1484      //4. done, commit
1485      if (openNewTrans) {
1486        commitTrans();
1487      }
1488    }
1489    catch(PersistenceException pe) {
1490      transFailed = true;
1491      throw(pe);
1492    }
1493    finally {
1494      //problems?
1495      if (transFailed) {
1496        rollbackTrans();
1497      }
1498    }
1499
1500    // let the world know about it
1501    fireResourceWritten(
1502      new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN, lr, lr.getLRPersistenceId()));
1503  }
1504
1505  /**
1506   * Releases the exlusive lock on a resource from the persistent store.
1507   */
1508  protected User getLockingUser(LanguageResource lr)
1509    throws PersistenceException,SecurityException {
1510
1511    //0. preconditions
1512    Assert.assertNotNull(lr);
1513    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
1514                      lr instanceof DatabaseCorpusImpl);
1515    Assert.assertNotNull(lr.getLRPersistenceId());
1516    Assert.assertEquals(lr.getDataStore(),this);
1517
1518    //delegate
1519    return getLockingUser((Long)lr.getLRPersistenceId());
1520  }
1521
1522
1523
1524  /**
1525   * Releases the exlusive lock on a resource from the persistent store.
1526   */
1527  protected User getLockingUser(Long lrID)
1528  throws PersistenceException,SecurityException {
1529
1530    //1. check session
1531    if (null == this.session) {
1532      throw new SecurityException("session not set");
1533    }
1534
1535    if (false == this.ac.isValidSession(this.session)) {
1536      throw new SecurityException("invalid session supplied");
1537    }
1538
1539    //3. read from DB
1540    PreparedStatement pstmt = null;
1541    Long userID = null;
1542    ResultSet rs = null;
1543
1544    try {
1545
1546      String sql = null;
1547
1548      if (this.dbType == DBHelper.ORACLE_DB) {
1549        sql = "   select  nvl(lr_locking_user_id,0) as user_id" +
1550              "   from "+this.dbSchema+"t_lang_resource " +
1551              "   where   lr_id = ?";
1552      }
1553      else if (this.dbType == DBHelper.POSTGRES_DB) {
1554        sql = "   select  coalesce(lr_locking_user_id,0) as user_id" +
1555              "   from t_lang_resource " +
1556              "   where   lr_id = ?";
1557      }
1558      else {
1559        throw new IllegalArgumentException();
1560      }
1561
1562      pstmt = this.jdbcConn.prepareStatement(sql);
1563      pstmt.setLong(1,lrID.longValue());
1564      pstmt.execute();
1565      rs = pstmt.getResultSet();
1566
1567      if (false == rs.next()) {
1568        throw new PersistenceException("LR not found in DB");
1569      }
1570
1571      long result = rs.getLong("user_id");
1572
1573      return result == 0  ? null
1574                          : this.ac.findUser(new Long(result));
1575    }
1576    catch(SQLException sqle) {
1577      throw new PersistenceException("can't get locking user from DB : ["+ sqle.getMessage()+"]");
1578    }
1579    finally {
1580      DBHelper.cleanup(rs);
1581      DBHelper.cleanup(pstmt);
1582    }
1583  }
1584
1585  /** helper for sync() - saves a Corpus in the database */
1586  protected void syncCorpus(Corpus corp)
1587    throws PersistenceException,SecurityException {
1588
1589    //0. preconditions
1590    Assert.assertNotNull(corp);
1591    Assert.assertTrue(corp instanceof DatabaseCorpusImpl);
1592    Assert.assertEquals(this,corp.getDataStore());
1593    Assert.assertNotNull(corp.getLRPersistenceId());
1594
1595    EventAwareCorpus dbCorpus = (EventAwareCorpus)corp;
1596
1597    //1. sync the corpus name?
1598    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
1599      _syncLR(corp);
1600    }
1601
1602    //2. sync the corpus features?
1603    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
1604      _syncFeatures(corp);
1605    }
1606
1607    //2.5 get removed documents and detach (not remove) them from the corpus in the
1608    //database
1609    List removedDocLRIDs = dbCorpus.getRemovedDocuments();
1610    if (removedDocLRIDs.size() > 0) {
1611      _syncRemovedDocumentsFromCorpus(removedDocLRIDs,(Long)corp.getLRPersistenceId());
1612    }
1613
1614    //3. get all documents
1615    //--Iterator it = corp.iterator();
1616    Iterator it = dbCorpus.getLoadedDocuments().iterator();
1617
1618    while (it.hasNext()) {
1619      Document dbDoc = (Document)it.next();
1620      //note - document may be NULL which means it was not loaded (load on demand)
1621      //just ignore it then
1622      if (null == dbDoc) {
1623        continue;
1624      }
1625
1626      //adopt/sync?
1627      if (null == dbDoc.getLRPersistenceId()) {
1628        //doc was never adopted, adopt it
1629
1630        //3.1 remove the transient doc from the corpus
1631        it.remove();
1632
1633        //3.2 get the security info for the corpus
1634        SecurityInfo si = getSecurityInfo(corp);
1635
1636
1637        Document adoptedDoc = null;
1638        try {
1639          //3.3. adopt the doc with the sec info
1640//System.out.println("adopting ["+dbDoc.getName()+"] ...");
1641          //don't open a new transaction, since sync() already has opended one
1642          adoptedDoc = (Document)_adopt(dbDoc,si,true);
1643
1644          //3.4. add doc to corpus in DB
1645          addDocumentToCorpus((Long)adoptedDoc.getLRPersistenceId(),
1646                              (Long)corp.getLRPersistenceId());
1647        }
1648        catch(SecurityException se) {
1649          throw new PersistenceException(se);
1650        }
1651
1652        //3.5 add back to corpus the new DatabaseDocument
1653        corp.add(adoptedDoc);
1654      }
1655      else {
1656        //don't open a new transaction, the sync() called for corpus has already
1657        //opened one
1658        try {
1659          _sync(dbDoc,true);
1660
1661          // let the world know about it
1662          fireResourceWritten( new DatastoreEvent(this,
1663                                                  DatastoreEvent.RESOURCE_WRITTEN,
1664                                                  dbDoc,
1665                                                  dbDoc.getLRPersistenceId()
1666                                                  )
1667                              );
1668
1669          //if the document is form the same DS but did not belong to the corpus add it now
1670          //NOTE: if the document already belongs to the corpus then nothing will be changed
1671          //in the DB
1672          addDocumentToCorpus((Long)dbDoc.getLRPersistenceId(),
1673                              (Long)corp.getLRPersistenceId());
1674        }
1675        catch(SecurityException se) {
1676          gate.util.Err.prln("document cannot be synced: ["+se.getMessage()+"]");
1677        }
1678      }
1679    }
1680  }
1681
1682  /** helper for sync() - saves a Document in the database */
1683  /** helper for sync() - saves a Document in the database */
1684  protected void syncDocument(Document doc)
1685    throws PersistenceException, SecurityException {
1686
1687    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1688    Assert.assertTrue(doc.getLRPersistenceId() instanceof Long);
1689
1690    Long lrID = (Long)doc.getLRPersistenceId();
1691    EventAwareLanguageResource dbDoc = (EventAwareLanguageResource)doc;
1692    //1. sync LR
1693    // only name can be changed here
1694    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
1695      _syncLR(doc);
1696    }
1697
1698    //2. sync Document
1699    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_MAIN)) {
1700      _syncDocumentHeader(doc);
1701    }
1702
1703    //3. [optional] sync Content
1704    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_CONTENT)) {
1705      _syncDocumentContent(doc);
1706    }
1707
1708    //4. [optional] sync Features
1709    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
1710      _syncFeatures(doc);
1711    }
1712
1713    //5. [optional] delete from DB named sets that were removed from the document
1714    Collection removedSets = ((EventAwareDocument)dbDoc).getRemovedAnnotationSets();
1715    Collection addedSets = ((EventAwareDocument)dbDoc).getAddedAnnotationSets();
1716    if (false == removedSets.isEmpty() || false == addedSets.isEmpty()) {
1717      _syncAnnotationSets(doc,removedSets,addedSets);
1718    }
1719
1720    //6. [optional] sync Annotations
1721    _syncAnnotations(doc);
1722  }
1723
1724
1725  /**
1726   *  helper for sync()
1727   *  NEVER call directly
1728   */
1729  protected abstract void _syncLR(LanguageResource lr)
1730    throws PersistenceException,SecurityException;
1731
1732  /** helper for sync() - never call directly */
1733  protected abstract void _syncDocumentHeader(Document doc)
1734    throws PersistenceException;
1735
1736  /** helper for sync() - never call directly */
1737  protected abstract void _syncDocumentContent(Document doc)
1738    throws PersistenceException;
1739
1740  /** helper for sync() - never call directly */
1741  protected abstract void _syncFeatures(LanguageResource lr)
1742    throws PersistenceException;
1743
1744  /** helper for sync() - never call directly */
1745  protected void _syncAnnotationSets(Document doc,Collection removedSets,Collection addedSets)
1746    throws PersistenceException {
1747
1748    //0. preconditions
1749    Assert.assertNotNull(doc);
1750    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1751    Assert.assertNotNull(doc.getLRPersistenceId());
1752    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
1753                      this.getDatabaseID());
1754    Assert.assertNotNull(removedSets);
1755    Assert.assertNotNull(addedSets);
1756
1757    Long lrID = (Long)doc.getLRPersistenceId();
1758
1759    //1. delete from DB removed a-sets
1760    PreparedStatement stmt = null;
1761
1762    try {
1763
1764      if (this.dbType == DBHelper.ORACLE_DB) {
1765        stmt = this.jdbcConn.prepareCall("{ call "+this.dbSchema+"persist.delete_annotation_set(?,?) }");
1766      }
1767      else if (this.dbType == DBHelper.POSTGRES_DB) {
1768        stmt = this.jdbcConn.prepareStatement("select persist_delete_annotation_set(?,?)");
1769      }
1770      else {
1771        Assert.fail();
1772      }
1773
1774      Iterator it = removedSets.iterator();
1775      while (it.hasNext()) {
1776        String setName = (String)it.next();
1777        stmt.setLong(1,lrID.longValue());
1778        stmt.setString(2,setName);
1779        stmt.execute();
1780      }
1781    }
1782    catch(SQLException sqle) {
1783      throw new PersistenceException("can't remove annotation set from DB: ["+ sqle.getMessage()+"]");
1784    }
1785    finally {
1786      DBHelper.cleanup(stmt);
1787    }
1788
1789    //2. create in DB new a-sets
1790    Iterator it = addedSets.iterator();
1791    while (it.hasNext()) {
1792      String setName = (String)it.next();
1793      AnnotationSet aset = doc.getAnnotations(setName);
1794
1795      Assert.assertNotNull(aset);
1796      Assert.assertTrue(aset instanceof DatabaseAnnotationSetImpl);
1797
1798      createAnnotationSet(lrID,aset);
1799    }
1800  }
1801
1802
1803  /** helper for sync() - never call directly */
1804  protected void _syncAnnotations(Document doc)
1805    throws PersistenceException {
1806
1807    //0. preconditions
1808    Assert.assertNotNull(doc);
1809    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1810    Assert.assertNotNull(doc.getLRPersistenceId());
1811    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
1812                      this.getDatabaseID());
1813
1814
1815    EventAwareDocument ead = (EventAwareDocument)doc;
1816    //1. get the sets read from the DB for this document
1817    //chnaged annotations can occur only in such sets
1818    Collection loadedSets = ead.getLoadedAnnotationSets();
1819
1820    Iterator it = loadedSets.iterator();
1821    while (it.hasNext()) {
1822      AnnotationSet as = (AnnotationSet)it.next();
1823      //check that this set is neither NEW nor DELETED
1824      //they should be already synced
1825      if (ead.getAddedAnnotationSets().contains(as.getName()) ||
1826          ead.getRemovedAnnotationSets().contains(as.getName())) {
1827        //oops, ignore it
1828        continue;
1829      }
1830
1831      EventAwareAnnotationSet eas = (EventAwareAnnotationSet)as;
1832      Assert.assertNotNull(as);
1833
1834      Collection anns = null;
1835      anns = eas.getAddedAnnotations();
1836      Assert.assertNotNull(anns);
1837      if (anns.size()>0) {
1838        _syncAddedAnnotations(doc,as,anns);
1839      }
1840
1841      anns = eas.getRemovedAnnotations();
1842      Assert.assertNotNull(anns);
1843      if (anns.size()>0) {
1844        _syncRemovedAnnotations(doc,as,anns);
1845      }
1846
1847      anns = eas.getChangedAnnotations();
1848      Assert.assertNotNull(anns);
1849      if (anns.size()>0) {
1850        _syncChangedAnnotations(doc,as,anns);
1851      }
1852    }
1853  }
1854
1855  /** helper for sync() - never call directly */
1856  protected void _syncAddedAnnotations(Document doc, AnnotationSet as, Collection changes)
1857    throws PersistenceException {
1858
1859    //0.preconditions
1860    Assert.assertNotNull(doc);
1861    Assert.assertNotNull(as);
1862    Assert.assertNotNull(changes);
1863    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1864    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
1865    Assert.assertTrue(changes.size() > 0);
1866
1867
1868    PreparedStatement pstmt = null;
1869    ResultSet rs = null;
1870    CallableStatement cstmt = null;
1871    Long lrID = (Long)doc.getLRPersistenceId();
1872    Long asetID = null;
1873
1874    try {
1875      //1. get the a-set ID in the database
1876      String sql = " select as_id  " +
1877                   " from  "+this.dbSchema+"v_annotation_set " +
1878                   " where  lr_id = ? ";
1879      //do we have aset name?
1880      String clause = null;
1881      String name = as.getName();
1882      if (null != name) {
1883        clause =   "        and as_name = ? ";
1884      }
1885      else {
1886        clause =   "        and as_name is null ";
1887      }
1888      sql = sql + clause;
1889
1890      pstmt = this.jdbcConn.prepareStatement(sql);
1891      pstmt.setLong(1,lrID.longValue());
1892      if (null != name) {
1893        pstmt.setString(2,name);
1894      }
1895      pstmt.execute();
1896      rs = pstmt.getResultSet();
1897
1898      if (rs.next()) {
1899        asetID = new Long(rs.getLong("as_id"));
1900      }
1901      else {
1902        throw new PersistenceException("cannot find annotation set with" +
1903                                      " name=["+name+"] , LRID=["+lrID+"] in database");
1904      }
1905
1906      //cleanup
1907      DBHelper.cleanup(rs);
1908      DBHelper.cleanup(pstmt);
1909
1910      //3. insert the new annotations from this set
1911
1912      //3.1. prepare call
1913      if (this.dbType == DBHelper.ORACLE_DB) {
1914
1915        cstmt = this.jdbcConn.prepareCall(
1916                "{ call "+Gate.DB_OWNER+".persist.create_annotation(?,?,?,?,?,?,?,?,?) }");
1917
1918        Long annGlobalID = null;
1919        Iterator it = changes.iterator();
1920
1921        while (it.hasNext()) {
1922
1923          //3.2. insert annotation
1924          Annotation ann = (Annotation)it.next();
1925
1926          Node start = (Node)ann.getStartNode();
1927          Node end = (Node)ann.getEndNode();
1928          String type = ann.getType();
1929
1930          cstmt.setLong(1,lrID.longValue());
1931          cstmt.setLong(2,ann.getId().longValue());
1932          cstmt.setLong(3,asetID.longValue());
1933          cstmt.setLong(4,start.getId().longValue());
1934          cstmt.setLong(5,start.getOffset().longValue());
1935          cstmt.setLong(6,end.getId().longValue());
1936          cstmt.setLong(7,end.getOffset().longValue());
1937          cstmt.setString(8,type);
1938          cstmt.registerOutParameter(9,java.sql.Types.BIGINT);
1939
1940          cstmt.execute();
1941          annGlobalID = new Long(cstmt.getLong(9));
1942
1943          //3.3. set annotation features
1944          FeatureMap features = ann.getFeatures();
1945          Assert.assertNotNull(features);
1946
1947          if (this.dbType == DBHelper.ORACLE_DB) {
1948            createFeaturesBulk(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1949          }
1950          else if (this.dbType == DBHelper.POSTGRES_DB) {
1951            createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1952          }
1953          else {
1954            Assert.fail();
1955          }
1956        }
1957      }
1958      else if (this.dbType == DBHelper.POSTGRES_DB) {
1959
1960        sql = "select persist_create_annotation(?,?,?,?,?,?,?,?)";
1961        pstmt = this.jdbcConn.prepareStatement(sql);
1962
1963        Long annGlobalID = null;
1964        Iterator it = changes.iterator();
1965
1966        while (it.hasNext()) {
1967
1968          //3.2. insert annotation
1969          Annotation ann = (Annotation)it.next();
1970
1971          Node start = (Node)ann.getStartNode();
1972          Node end = (Node)ann.getEndNode();
1973          String type = ann.getType();
1974
1975          pstmt.setLong(1,lrID.longValue());
1976          pstmt.setLong(2,ann.getId().longValue());
1977          pstmt.setLong(3,asetID.longValue());
1978          pstmt.setLong(4,start.getId().longValue());
1979          pstmt.setLong(5,start.getOffset().longValue());
1980          pstmt.setLong(6,end.getId().longValue());
1981          pstmt.setLong(7,end.getOffset().longValue());
1982          pstmt.setString(8,type);
1983          pstmt.execute();
1984
1985          rs = pstmt.getResultSet();
1986
1987          if (false == rs.next()) {
1988            throw new PersistenceException("empty result set");
1989          }
1990          annGlobalID = new Long(rs.getLong(1));
1991
1992          //3.3. set annotation features
1993          FeatureMap features = ann.getFeatures();
1994          Assert.assertNotNull(features);
1995          createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1996        }
1997      }
1998
1999      else {
2000        throw new IllegalArgumentException();
2001      }
2002
2003    }
2004    catch(SQLException sqle) {
2005      throw new PersistenceException("can't add annotations in DB : ["+
2006                                      sqle.getMessage()+"]");
2007    }
2008    finally {
2009      DBHelper.cleanup(rs);
2010      DBHelper.cleanup(pstmt);
2011      DBHelper.cleanup(cstmt);
2012    }
2013  }
2014
2015  /** helper for sync() - never call directly */
2016  protected void _syncRemovedAnnotations(Document doc,AnnotationSet as, Collection changes)
2017    throws PersistenceException {
2018
2019    //0.preconditions
2020    Assert.assertNotNull(doc);
2021    Assert.assertNotNull(as);
2022    Assert.assertNotNull(changes);
2023    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2024    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
2025    Assert.assertTrue(changes.size() > 0);
2026
2027
2028    PreparedStatement pstmt = null;
2029    ResultSet rs = null;
2030    Long lrID = (Long)doc.getLRPersistenceId();
2031    Long docID = null;
2032    Long asetID = null;
2033
2034    try {
2035      //1. get the a-set ID in the database
2036      String sql = " select as_id,  " +
2037                   "        as_doc_id " +
2038                   " from  "+this.dbSchema+"v_annotation_set " +
2039                   " where  lr_id = ? ";
2040      //do we have aset name?
2041      String clause = null;
2042      String name = as.getName();
2043      if (null != name) {
2044        clause =   "        and as_name = ? ";
2045      }
2046      else {
2047        clause =   "        and as_name is null ";
2048      }
2049      sql = sql + clause;
2050
2051      pstmt = this.jdbcConn.prepareStatement(sql);
2052      pstmt.setLong(1,lrID.longValue());
2053      if (null != name) {
2054        pstmt.setString(2,name);
2055      }
2056      pstmt.execute();
2057      rs = pstmt.getResultSet();
2058
2059      if (rs.next()) {
2060        asetID = new Long(rs.getLong("as_id"));
2061        docID = new Long(rs.getLong("as_doc_id"));
2062      }
2063      else {
2064        throw new PersistenceException("cannot find annotation set with" +
2065                                      " name=["+name+"] , LRID=["+lrID+"] in database");
2066      }
2067
2068      //3. delete the removed annotations from this set
2069
2070      //cleanup
2071      DBHelper.cleanup(rs);
2072      DBHelper.cleanup(pstmt);
2073
2074      //3.1. prepare call
2075
2076      if (this.dbType == DBHelper.ORACLE_DB) {
2077        pstmt = this.jdbcConn.prepareCall("{ call "+this.dbSchema+"persist.delete_annotation(?,?) }");
2078      }
2079      else if (this.dbType == DBHelper.POSTGRES_DB) {
2080        pstmt = this.jdbcConn.prepareStatement("select persist_delete_annotation(?,?)");
2081      }
2082      else {
2083        throw new IllegalArgumentException();
2084      }
2085
2086      Iterator it = changes.iterator();
2087
2088      while (it.hasNext()) {
2089
2090        //3.2. insert annotation
2091        Annotation ann = (Annotation)it.next();
2092
2093        pstmt.setLong(1,docID.longValue()); //annotations are linked with documents, not LRs!
2094        pstmt.setLong(2,ann.getId().longValue());
2095        pstmt.execute();
2096      }
2097    }
2098    catch(SQLException sqle) {
2099      throw new PersistenceException("can't delete annotations in DB : ["+
2100                                      sqle.getMessage()+"]");
2101    }
2102    finally {
2103      DBHelper.cleanup(rs);
2104      DBHelper.cleanup(pstmt);
2105    }
2106  }
2107
2108
2109  /** helper for sync() - never call directly */
2110  protected void _syncChangedAnnotations(Document doc,AnnotationSet as, Collection changes)
2111    throws PersistenceException {
2112
2113    //technically this approach sux
2114    //at least it works
2115
2116    //1. delete
2117    _syncRemovedAnnotations(doc,as,changes);
2118    //2. recreate
2119    _syncAddedAnnotations(doc,as,changes);
2120  }
2121
2122  /**
2123   * Get a resource from the persistent store.
2124   * <B>Don't use this method - use Factory.createResource with
2125   * DataStore and DataStoreInstanceId parameters set instead.</B>
2126   */
2127  public LanguageResource getLr(String lrClassName, Object lrPersistenceId)
2128  throws PersistenceException,SecurityException {
2129
2130    LanguageResource result = null;
2131
2132    //0. preconditions
2133    Assert.assertNotNull(lrPersistenceId);
2134
2135    //1. check session
2136    if (null == this.session) {
2137      throw new SecurityException("session not set");
2138    }
2139
2140    if (false == this.ac.isValidSession(this.session)) {
2141      throw new SecurityException("invalid session supplied");
2142    }
2143
2144    //2. check permissions
2145    if (false == canReadLR(lrPersistenceId)) {
2146      throw new SecurityException("insufficient privileges");
2147    }
2148
2149    //3. get resource from DB
2150    if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
2151      result = readDocument(lrPersistenceId);
2152      Assert.assertTrue(result instanceof DatabaseDocumentImpl);
2153    }
2154    else if (lrClassName.equals(DBHelper.CORPUS_CLASS)) {
2155      result = readCorpus(lrPersistenceId);
2156      Assert.assertTrue(result instanceof DatabaseCorpusImpl);
2157    }
2158    else {
2159      throw new IllegalArgumentException("resource class should be either Document or Corpus");
2160    }
2161
2162    //4. postconditions
2163    Assert.assertNotNull(result.getDataStore());
2164    Assert.assertTrue(result.getDataStore() instanceof DatabaseDataStore);
2165    Assert.assertNotNull(result.getLRPersistenceId());
2166
2167    //5. register the read doc as listener for sync events
2168    addDatastoreListener((DatastoreListener)result);
2169
2170    //6. add the resource to the list of dependent resources - i.e. the ones that the
2171    //data store should take care upon closing [and call sync()]
2172    this.dependentResources.add(result);
2173
2174    //7. done
2175    return result;
2176  }
2177
2178  /** helper method for getLR - reads LR of type Document */
2179  private DatabaseDocumentImpl readDocument(Object lrPersistenceId)
2180    throws PersistenceException {
2181
2182    //0. preconditions
2183    Assert.assertNotNull(lrPersistenceId);
2184
2185    if (false == lrPersistenceId instanceof Long) {
2186      throw new IllegalArgumentException();
2187    }
2188
2189    // 1. dummy document to be initialized
2190    DatabaseDocumentImpl result = new DatabaseDocumentImpl(this.jdbcConn);
2191
2192    PreparedStatement pstmt = null;
2193    ResultSet rs = null;
2194
2195    //3. read from DB
2196    try {
2197      String sql = " select lr_name, " +
2198                   "        lrtp_type, " +
2199                   "        lr_id, " +
2200                   "        lr_parent_id, " +
2201                   "        doc_id, " +
2202                   "        doc_url, " +
2203                   "        doc_start, " +
2204                   "        doc_end, " +
2205                   "        doc_is_markup_aware " +
2206                   " from  "+this.dbSchema+"v_document " +
2207                   " where  lr_id = ? ";
2208
2209      pstmt = this.jdbcConn.prepareStatement(sql);
2210      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2211      pstmt.execute();
2212      rs = pstmt.getResultSet();
2213
2214      if (false == rs.next()) {
2215        //ooops mo data found
2216        throw new PersistenceException("Invalid LR ID supplied - no data found");
2217      }
2218
2219      //4. fill data
2220
2221      //4.0 name
2222      String lrName = rs.getString("lr_name");
2223      Assert.assertNotNull(lrName);
2224      result.setName(lrName);
2225
2226      //4.1 parent
2227      Long parentID = null;
2228      long parent_id = rs.getLong("lr_parent_id");
2229      if (false == rs.wasNull()) {
2230        parentID = new Long(parent_id);
2231
2232        //read parent resource
2233        LanguageResource parentLR = this.getLr(DBHelper.DOCUMENT_CLASS,parentID);
2234        Assert.assertNotNull(parentLR);
2235        Assert.assertTrue(parentLR instanceof DatabaseDocumentImpl);
2236
2237        result.setParent(parentLR);
2238      }
2239
2240
2241      //4.2. markup aware
2242      if (this.dbType == DBHelper.ORACLE_DB) {
2243        long markup = rs.getLong("doc_is_markup_aware");
2244        Assert.assertTrue(markup == DBHelper.FALSE || markup == DBHelper.TRUE);
2245        if (markup == DBHelper.FALSE) {
2246          result.setMarkupAware(Boolean.FALSE);
2247        }
2248        else {
2249          result.setMarkupAware(Boolean.TRUE);
2250
2251        }
2252      }
2253      else if (this.dbType == DBHelper.POSTGRES_DB) {
2254        boolean markup = rs.getBoolean("doc_is_markup_aware");
2255        result.setMarkupAware(new Boolean(markup));
2256      }
2257      else {
2258        throw new IllegalArgumentException();
2259      }
2260
2261
2262      //4.3 datastore
2263      result.setDataStore(this);
2264
2265      //4.4. persist ID
2266      Long persistID = new Long(rs.getLong("lr_id"));
2267      result.setLRPersistenceId(persistID);
2268
2269      //4.5  source url
2270      String url = rs.getString("doc_url");
2271      if(url != null && url.length() > 0) result.setSourceUrl(new URL(url));
2272
2273      //4.6. start offset
2274      Long start = null;
2275      long longVal = rs.getLong("doc_start");
2276      //null?
2277      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2278      if (false == rs.wasNull()) {
2279        start = new Long(longVal);
2280      }
2281      result.setSourceUrlStartOffset(start);
2282//      initData.put("DOC_SOURCE_URL_START",start);
2283
2284      //4.7. end offset
2285      Long end = null;
2286      longVal = rs.getLong("doc_end");
2287      //null?
2288      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2289      if (false == rs.wasNull()) {
2290        end = new Long(longVal);
2291      }
2292      result.setSourceUrlEndOffset(end);
2293//      initData.put("DOC_SOURCE_URL_END",end);
2294
2295      //4.8 features
2296      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_DOCUMENT);
2297      result.setFeatures(features);
2298      //initData.put("DOC_FEATURES",features);
2299
2300      //4.9 set the nextAnnotationID correctly
2301      long doc_id = rs.getLong("doc_id");
2302
2303      //cleanup
2304      DBHelper.cleanup(rs);
2305      DBHelper.cleanup(pstmt);
2306
2307      sql = " select  max(ann_local_id),'ann_id'" +
2308            " from "+this.dbSchema+"t_annotation " +
2309            " where ann_doc_id = ?" +
2310            " union " +
2311            " select max(node_local_id),'node_id' " +
2312            " from "+this.dbSchema+"t_node " +
2313            " where node_doc_id = ?";
2314
2315      pstmt = this.jdbcConn.prepareStatement(sql);
2316      pstmt.setLong(1,doc_id);
2317      pstmt.setLong(2,doc_id);
2318      pstmt.execute();
2319      rs = pstmt.getResultSet();
2320
2321      int maxAnnID = 0 , maxNodeID = 0;
2322      //ann id
2323      if (false == rs.next()) {
2324        //ooops no data found
2325        throw new PersistenceException("Invalid LR ID supplied - no data found");
2326      }
2327      if (rs.getString(2).equals("ann_id"))
2328        maxAnnID = rs.getInt(1);
2329      else
2330        maxNodeID = rs.getInt(1);
2331
2332      if (false == rs.next()) {
2333        //ooops no data found
2334        throw new PersistenceException("Invalid LR ID supplied - no data found");
2335      }
2336      if (rs.getString(2).equals("node_id"))
2337        maxNodeID = rs.getInt(1);
2338      else
2339        maxAnnID = rs.getInt(1);
2340
2341      result.setNextNodeId(maxNodeID+1);
2342//      initData.put("DOC_NEXT_NODE_ID",new Integer(maxNodeID+1));
2343      result.setNextAnnotationId(maxAnnID+1);
2344//      initData.put("DOC_NEXT_ANN_ID",new Integer(maxAnnID+1));
2345
2346
2347//      params.put("initData__$$__", initData);
2348//      try {
2349        //here we create the persistent LR via Factory, so it's registered
2350        //in GATE
2351//        result = (DatabaseDocumentImpl)Factory.createResource("gate.corpora.DatabaseDocumentImpl", params);
2352//      }
2353//      catch (gate.creole.ResourceInstantiationException ex) {
2354//        throw new GateRuntimeException(ex.getMessage());
2355//      }
2356    }
2357    catch(SQLException sqle) {
2358      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2359    }
2360    catch(Exception e) {
2361      throw new PersistenceException(e);
2362    }
2363    finally {
2364      DBHelper.cleanup(rs);
2365      DBHelper.cleanup(pstmt);
2366    }
2367
2368    return result;
2369  }
2370
2371
2372  /**
2373   *  helper method for getLR - reads LR of type Corpus
2374   */
2375  private DatabaseCorpusImpl readCorpus(Object lrPersistenceId)
2376    throws PersistenceException {
2377
2378    //0. preconditions
2379    Assert.assertNotNull(lrPersistenceId);
2380
2381    if (false == lrPersistenceId instanceof Long) {
2382      throw new IllegalArgumentException();
2383    }
2384
2385    //3. read from DB
2386    PreparedStatement pstmt = null;
2387    ResultSet rs = null;
2388    DatabaseCorpusImpl result = null;
2389
2390    try {
2391      String sql = " select lr_name " +
2392                   " from  "+this.dbSchema+"t_lang_resource " +
2393                   " where  lr_id = ? ";
2394      pstmt = this.jdbcConn.prepareStatement(sql);
2395      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2396      pstmt.execute();
2397      rs = pstmt.getResultSet();
2398
2399      if (false == rs.next()) {
2400        //ooops mo data found
2401        throw new PersistenceException("Invalid LR ID supplied - no data found");
2402      }
2403
2404      //4. fill data
2405
2406      //4.1 name
2407      String lrName = rs.getString("lr_name");
2408      Assert.assertNotNull(lrName);
2409
2410      //4.8 features
2411      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_CORPUS);
2412
2413      //4.9 cleanup
2414      DBHelper.cleanup(rs);
2415      DBHelper.cleanup(pstmt);
2416
2417      sql = " select lr_id ," +
2418            "         lr_name " +
2419            " from "+this.dbSchema+"t_document        doc, " +
2420            "      "+this.dbSchema+"t_lang_resource   lr, " +
2421            "      "+this.dbSchema+"t_corpus_document corpdoc, " +
2422            "      "+this.dbSchema+"t_corpus          corp " +
2423            " where lr.lr_id = doc.doc_lr_id " +
2424            "       and doc.doc_id = corpdoc.cd_doc_id " +
2425            "       and corpdoc.cd_corp_id = corp.corp_id " +
2426            "       and corp_lr_id = ? ";
2427      pstmt = this.jdbcConn.prepareStatement(sql);
2428      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2429      pstmt.execute();
2430      rs = pstmt.getResultSet();
2431
2432      Vector documentData = new Vector();
2433      while (rs.next()) {
2434        Long docLRID = new Long(rs.getLong("lr_id"));
2435        String docName = rs.getString("lr_name");
2436        documentData.add(new DocumentData(docName, docLRID));
2437      }
2438      DBHelper.cleanup(rs);
2439      DBHelper.cleanup(pstmt);
2440
2441      result = new DatabaseCorpusImpl(lrName,
2442                                      this,
2443                                      (Long)lrPersistenceId,
2444                                      features,
2445                                      documentData);
2446    }
2447    catch(SQLException sqle) {
2448      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2449    }
2450    catch(Exception e) {
2451      throw new PersistenceException(e);
2452    }
2453    finally {
2454      DBHelper.cleanup(rs);
2455      DBHelper.cleanup(pstmt);
2456    }
2457
2458    return result;
2459  }
2460
2461  /**
2462   *  reads the features of an entity
2463   *  entities are of type LR or Annotation
2464   */
2465  protected abstract FeatureMap readFeatures(Long entityID, int entityType)
2466    throws PersistenceException;
2467
2468  /**
2469   *  helper method for delete()
2470   *  never call it directly beause proper events will not be fired
2471   */
2472  protected abstract void deleteDocument(Long lrId)
2473    throws PersistenceException;
2474
2475  /**
2476   *  helper method for delete()
2477   *  never call it directly beause proper events will not be fired
2478   */
2479  protected abstract void deleteCorpus(Long lrId)
2480    throws PersistenceException;
2481
2482  /**
2483   *   unloads a LR from the GUI
2484   */
2485  protected void unloadLR(Long lrID)
2486  throws GateException{
2487
2488    //0. preconfitions
2489    Assert.assertNotNull(lrID);
2490
2491    //1. get all LRs in the system
2492    List resources = Gate.getCreoleRegister().getAllInstances("gate.LanguageResource");
2493
2494    Iterator it = resources.iterator();
2495    while (it.hasNext()) {
2496      LanguageResource lr = (LanguageResource)it.next();
2497      if (lrID.equals(lr.getLRPersistenceId()) &&
2498          this.equals(lr.getDataStore())) {
2499        //found it - unload it
2500        Factory.deleteResource(lr);
2501        break;
2502      }
2503    }
2504  }
2505
2506  /** helper for sync() - never call directly */
2507  protected abstract void _syncRemovedDocumentsFromCorpus(List docLRIDs, Long corpLRID)
2508    throws PersistenceException;
2509
2510  /**
2511   *   adds document to corpus in the database
2512   *   if the document is already part of the corpus nothing
2513   *   changes
2514   */
2515  protected abstract void addDocumentToCorpus(Long docID,Long corpID)
2516  throws PersistenceException,SecurityException;
2517
2518
2519}
2520