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.87 2003/07/05 14:22:45 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  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
1137    while (itDocuments.hasNext()) {
1138      Document doc = (Document)itDocuments.next();
1139
1140      //3.1. ensure that the document is either transient or is from the ...
1141      // same DataStore
1142      if (doc.getLRPersistenceId() == null) {
1143        //transient document
1144
1145        //now this is a bit ugly patch, the transaction related functionality
1146        //should not be in this method
1147        if (newTransPerDocument) {
1148          beginTrans();
1149        }
1150
1151        //do call iterator::remove before the call to createDocument because
1152        //...there is a factory::deleteResource() call for the transient document there
1153        //...and the iterator gets confused
1154        itDocuments.remove();
1155
1156        //create doc in database and return DB ddoc
1157        Document dbDoc = createDocument(doc,corpusID,secInfo);
1158
1159        if (newTransPerDocument) {
1160          commitTrans();
1161        }
1162
1163        dbDocs.add(dbDoc);
1164        //8. let the world know
1165        fireResourceAdopted(new DatastoreEvent(this,
1166                                                DatastoreEvent.RESOURCE_ADOPTED,
1167                                                dbDoc,
1168                                                dbDoc.getLRPersistenceId()
1169                                              )
1170                            );
1171
1172        //9. fire also resource written event because it's now saved
1173        fireResourceWritten(new DatastoreEvent(this,
1174                                                DatastoreEvent.RESOURCE_WRITTEN,
1175                                                dbDoc,
1176                                                dbDoc.getLRPersistenceId()
1177                                              )
1178                           );
1179
1180        //10.
1181        //DON'T make explicit Factory call, since createDocument called above
1182        ///...takes care to call Factory.deleteResource for the transient document
1183      }
1184      else if (doc.getDataStore().equals(this)) {
1185        //persistent doc from the same DataStore
1186        fireResourceAdopted(
1187            new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
1188                               doc,
1189                               doc.getLRPersistenceId()));
1190
1191        //6. fire also resource written event because it's now saved
1192        fireResourceWritten(
1193          new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
1194                              doc,
1195                              doc.getLRPersistenceId()));
1196      }
1197      else {
1198        //persistent doc from other datastore
1199        //skip
1200        gate.util.Err.prln("document ["+doc.getLRPersistenceId()+"] is adopted from another "+
1201                            " datastore. Skipped.");
1202      }
1203    }
1204
1205    //4. create features
1206    if (this.dbType == DBHelper.ORACLE_DB) {
1207      createFeaturesBulk(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1208    }
1209    else if (this.dbType == DBHelper.POSTGRES_DB) {
1210      createFeatures(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1211    }
1212    else {
1213      Assert.fail();
1214    }
1215
1216
1217    //5. create a DatabaseCorpusImpl and return it
1218///    Corpus dbCorpus = new DatabaseCorpusImpl(corp.getName(),
1219///                                             this,
1220///                                              lrID,
1221///                                              corp.getFeatures(),
1222///                                              dbDocs);
1223///
1224
1225    Corpus dbCorpus = null;
1226    FeatureMap params = Factory.newFeatureMap();
1227    HashMap initData = new HashMap();
1228
1229    initData.put("DS",this);
1230    initData.put("LR_ID",lrID);
1231    initData.put("CORP_NAME",corp.getName());
1232    initData.put("CORP_FEATURES",corp.getFeatures());
1233    initData.put("CORP_SUPPORT_LIST",dbDocs);
1234
1235    params.put("initData__$$__", initData);
1236
1237    try {
1238      //here we create the persistent LR via Factory, so it's registered
1239      //in GATE
1240      dbCorpus = (Corpus)Factory.createResource("gate.corpora.DatabaseCorpusImpl", params);
1241    }
1242    catch (gate.creole.ResourceInstantiationException ex) {
1243      throw new GateRuntimeException(ex.getMessage());
1244    }
1245
1246    //6. done
1247    return dbCorpus;
1248  }
1249
1250  /**
1251   * helper for adopt
1252   * creates a LR of type Document
1253   */
1254  protected Document createDocument(Document doc,SecurityInfo secInfo)
1255  throws PersistenceException,SecurityException {
1256
1257    //delegate, set to Null
1258    return createDocument(doc,null,secInfo);
1259  }
1260
1261
1262  /**
1263   * helper for adopt
1264   * creates a LR of type Document
1265   */
1266  protected Document createDocument(Document doc, Long corpusID,SecurityInfo secInfo)
1267  throws PersistenceException,SecurityException {
1268
1269    //-1. preconditions
1270    Assert.assertNotNull(doc);
1271    Assert.assertNotNull(secInfo);
1272
1273    //0. check securoity settings
1274    if (false == this.ac.isValidSecurityInfo(secInfo)) {
1275      throw new SecurityException("Invalid security settings");
1276    }
1277
1278    //1. get the data to be stored
1279    AnnotationSet defaultAnnotations = doc.getAnnotations();
1280    DocumentContent docContent = doc.getContent();
1281    FeatureMap docFeatures = doc.getFeatures();
1282    String docName  = doc.getName();
1283    URL docURL = doc.getSourceUrl();
1284    Boolean docIsMarkupAware = doc.getMarkupAware();
1285    Long docStartOffset = doc.getSourceUrlStartOffset();
1286    Long docEndOffset = doc.getSourceUrlEndOffset();
1287    String docEncoding = null;
1288    try {
1289      docEncoding = (String)doc.
1290        getParameterValue(Document.DOCUMENT_ENCODING_PARAMETER_NAME);
1291    }
1292    catch(gate.creole.ResourceInstantiationException re) {
1293      throw new PersistenceException("cannot create document: error getting " +
1294                                     " document encoding ["+re.getMessage()+"]");
1295    }
1296
1297
1298    //3. create a Language Resource (an entry in T_LANG_RESOURCE) for this document
1299    Long lrID = createLR(DBHelper.DOCUMENT_CLASS,docName,secInfo,null);
1300
1301    //4. create a record in T_DOCUMENT for this document
1302    Long docID = createDoc(lrID,
1303                            docURL,
1304                            docEncoding,
1305                            docStartOffset,
1306                            docEndOffset,
1307                            docIsMarkupAware,
1308                            corpusID);
1309
1310
1311    //5. fill document content (record[s] in T_DOC_CONTENT)
1312
1313    //do we have content at all?
1314    if (docContent.size().longValue() > 0) {
1315//      updateDocumentContent(docContentID,docContent);
1316      updateDocumentContent(docID,docContent);
1317    }
1318
1319    //6. insert annotations, etc
1320
1321    //6.1. create default annotation set
1322    createAnnotationSet(lrID,defaultAnnotations);
1323
1324    //6.2. create named annotation sets
1325    Map namedAnns = doc.getNamedAnnotationSets();
1326    //the map may be null
1327    if (null != namedAnns) {
1328      Set setAnns = namedAnns.entrySet();
1329      Iterator itAnns = setAnns.iterator();
1330
1331      while (itAnns.hasNext()) {
1332        Map.Entry mapEntry = (Map.Entry)itAnns.next();
1333        //String currAnnName = (String)mapEntry.getKey();
1334        AnnotationSet currAnnSet = (AnnotationSet)mapEntry.getValue();
1335
1336        //create a-sets
1337        createAnnotationSet(lrID,currAnnSet);
1338      }
1339    }
1340
1341    //7. create features
1342    if (this.dbType == DBHelper.ORACLE_DB) {
1343      createFeaturesBulk(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
1344    }
1345    else if (this.dbType == DBHelper.POSTGRES_DB) {
1346      createFeatures(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
1347    }
1348    else {
1349      Assert.fail();
1350    }
1351
1352
1353    //9. create a DatabaseDocument wrapper and return it
1354
1355/*    Document dbDoc = new DatabaseDocumentImpl(this.jdbcConn,
1356                                              doc.getName(),
1357                                              this,
1358                                              lrID,
1359                                              doc.getContent(),
1360                                              doc.getFeatures(),
1361                                              doc.getMarkupAware(),
1362                                              doc.getSourceUrl(),
1363                                              doc.getSourceUrlStartOffset(),
1364                                              doc.getSourceUrlEndOffset(),
1365                                              doc.getAnnotations(),
1366                                              doc.getNamedAnnotationSets());
1367*/
1368    Document dbDoc = null;
1369    FeatureMap params = Factory.newFeatureMap();
1370
1371    HashMap initData = new HashMap();
1372    initData.put("JDBC_CONN",this.jdbcConn);
1373    initData.put("DS",this);
1374    initData.put("LR_ID",lrID);
1375    initData.put("DOC_NAME",doc.getName());
1376    initData.put("DOC_CONTENT",doc.getContent());
1377    initData.put("DOC_FEATURES",doc.getFeatures());
1378    initData.put("DOC_MARKUP_AWARE",doc.getMarkupAware());
1379    initData.put("DOC_SOURCE_URL",doc.getSourceUrl());
1380    if(doc instanceof DocumentImpl){
1381      initData.put("DOC_STRING_CONTENT",
1382                   ((DocumentImpl)doc).getStringContent());
1383    }
1384    initData.put("DOC_SOURCE_URL_START",doc.getSourceUrlStartOffset());
1385    initData.put("DOC_SOURCE_URL_END",doc.getSourceUrlEndOffset());
1386    initData.put("DOC_DEFAULT_ANNOTATIONS",doc.getAnnotations());
1387    initData.put("DOC_NAMED_ANNOTATION_SETS",doc.getNamedAnnotationSets());
1388
1389    params.put("initData__$$__", initData);
1390
1391    try {
1392      //here we create the persistent LR via Factory, so it's registered
1393      //in GATE
1394      dbDoc = (Document)Factory.createResource("gate.corpora.DatabaseDocumentImpl", params);
1395    }
1396    catch (gate.creole.ResourceInstantiationException ex) {
1397      throw new GateRuntimeException(ex.getMessage());
1398    }
1399
1400    //unload the transient document
1401//System.out.println("unloading "+doc.getName() +"...");
1402    Factory.deleteResource(doc);
1403
1404    return dbDoc;
1405  }
1406
1407  protected abstract Long createLR(String lrType,
1408                          String lrName,
1409                          SecurityInfo si,
1410                          Long lrParentID)
1411    throws PersistenceException,SecurityException;
1412
1413
1414  protected abstract Long createDoc(Long _lrID,
1415                          URL _docURL,
1416                          String _docEncoding,
1417                          Long _docStartOffset,
1418                          Long _docEndOffset,
1419                          Boolean _docIsMarkupAware,
1420                          Long _corpusID)
1421    throws PersistenceException;
1422
1423  protected abstract void updateDocumentContent(Long docID,DocumentContent content)
1424    throws PersistenceException;
1425
1426  protected abstract void createAnnotationSet(Long lrID, AnnotationSet aset)
1427    throws PersistenceException;
1428
1429  protected abstract void createFeaturesBulk(Long entityID, int entityType, FeatureMap features)
1430    throws PersistenceException;
1431
1432  protected abstract void createFeatures(Long entityID, int entityType, FeatureMap features)
1433    throws PersistenceException;
1434
1435  /**
1436   * Save: synchonise the in-memory image of the LR with the persistent
1437   * image.
1438   */
1439  protected void _sync(LanguageResource lr, boolean openNewTrans)
1440    throws PersistenceException,SecurityException {
1441
1442    //0.preconditions
1443    Assert.assertNotNull(lr);
1444    Long lrID = (Long)lr.getLRPersistenceId();
1445
1446    if (false == lr instanceof Document &&
1447        false == lr instanceof Corpus) {
1448      //only documents and corpuses could be serialized in DB
1449      throw new IllegalArgumentException("only Documents and Corpuses could "+
1450                                          "be serialized in DB");
1451    }
1452
1453    // check that this LR is one of ours (i.e. has been adopted)
1454    if( null == lr.getDataStore() || false == lr.getDataStore().equals(this))
1455      throw new PersistenceException(
1456        "This LR is not stored in this DataStore"
1457      );
1458
1459
1460    //1. check session
1461    if (null == this.session) {
1462      throw new SecurityException("session not set");
1463    }
1464
1465    if (false == this.ac.isValidSession(this.session)) {
1466      throw new SecurityException("invalid session supplied");
1467    }
1468
1469    //2. check permissions
1470    if (false == canWriteLR(lrID)) {
1471      throw new SecurityException("insufficient privileges");
1472    }
1473
1474    //3. is the resource locked?
1475    User lockingUser = getLockingUser(lr);
1476    User currUser = this.session.getUser();
1477
1478    if (lockingUser != null && false == lockingUser.equals(currUser)) {
1479      throw new PersistenceException("document is locked by another user and cannot be synced");
1480    }
1481
1482
1483    boolean transFailed = false;
1484    try {
1485      //2. autocommit should be FALSE because of LOBs
1486      if (openNewTrans) {
1487        beginTrans();
1488      }
1489
1490      //3. perform changes, if anything goes wrong, rollback
1491      if (lr instanceof Document) {
1492        syncDocument((Document)lr);
1493      }
1494      else {
1495        syncCorpus((Corpus)lr);
1496      }
1497
1498      //4. done, commit
1499      if (openNewTrans) {
1500        commitTrans();
1501      }
1502    }
1503    catch(PersistenceException pe) {
1504      transFailed = true;
1505      throw(pe);
1506    }
1507    finally {
1508      //problems?
1509      if (transFailed) {
1510        rollbackTrans();
1511      }
1512    }
1513
1514    // let the world know about it
1515    fireResourceWritten(
1516      new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN, lr, lr.getLRPersistenceId()));
1517  }
1518
1519  /**
1520   * Releases the exlusive lock on a resource from the persistent store.
1521   */
1522  protected User getLockingUser(LanguageResource lr)
1523    throws PersistenceException,SecurityException {
1524
1525    //0. preconditions
1526    Assert.assertNotNull(lr);
1527    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
1528                      lr instanceof DatabaseCorpusImpl);
1529    Assert.assertNotNull(lr.getLRPersistenceId());
1530    Assert.assertEquals(lr.getDataStore(),this);
1531
1532    //delegate
1533    return getLockingUser((Long)lr.getLRPersistenceId());
1534  }
1535
1536
1537
1538  /**
1539   * Releases the exlusive lock on a resource from the persistent store.
1540   */
1541  protected User getLockingUser(Long lrID)
1542  throws PersistenceException,SecurityException {
1543
1544    //1. check session
1545    if (null == this.session) {
1546      throw new SecurityException("session not set");
1547    }
1548
1549    if (false == this.ac.isValidSession(this.session)) {
1550      throw new SecurityException("invalid session supplied");
1551    }
1552
1553    //3. read from DB
1554    PreparedStatement pstmt = null;
1555    Long userID = null;
1556    ResultSet rs = null;
1557
1558    try {
1559
1560      String sql = null;
1561
1562      if (this.dbType == DBHelper.ORACLE_DB) {
1563        sql = "   select  nvl(lr_locking_user_id,0) as user_id" +
1564              "   from "+this.dbSchema+"t_lang_resource " +
1565              "   where   lr_id = ?";
1566      }
1567      else if (this.dbType == DBHelper.POSTGRES_DB) {
1568        sql = "   select  coalesce(lr_locking_user_id,0) as user_id" +
1569              "   from t_lang_resource " +
1570              "   where   lr_id = ?";
1571      }
1572      else {
1573        throw new IllegalArgumentException();
1574      }
1575
1576      pstmt = this.jdbcConn.prepareStatement(sql);
1577      pstmt.setLong(1,lrID.longValue());
1578      pstmt.execute();
1579      rs = pstmt.getResultSet();
1580
1581      if (false == rs.next()) {
1582        throw new PersistenceException("LR not found in DB");
1583      }
1584
1585      long result = rs.getLong("user_id");
1586
1587      return result == 0  ? null
1588                          : this.ac.findUser(new Long(result));
1589    }
1590    catch(SQLException sqle) {
1591      throw new PersistenceException("can't get locking user from DB : ["+ sqle.getMessage()+"]");
1592    }
1593    finally {
1594      DBHelper.cleanup(rs);
1595      DBHelper.cleanup(pstmt);
1596    }
1597  }
1598
1599  /** helper for sync() - saves a Corpus in the database */
1600  protected void syncCorpus(Corpus corp)
1601    throws PersistenceException,SecurityException {
1602
1603    //0. preconditions
1604    Assert.assertNotNull(corp);
1605    Assert.assertTrue(corp instanceof DatabaseCorpusImpl);
1606    Assert.assertEquals(this,corp.getDataStore());
1607    Assert.assertNotNull(corp.getLRPersistenceId());
1608
1609    EventAwareCorpus dbCorpus = (EventAwareCorpus)corp;
1610
1611    //1. sync the corpus name?
1612    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
1613      _syncLR(corp);
1614    }
1615
1616    //2. sync the corpus features?
1617    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
1618      _syncFeatures(corp);
1619    }
1620
1621    //2.5 get removed documents and detach (not remove) them from the corpus in the
1622    //database
1623    List removedDocLRIDs = dbCorpus.getRemovedDocuments();
1624    if (removedDocLRIDs.size() > 0) {
1625      _syncRemovedDocumentsFromCorpus(removedDocLRIDs,(Long)corp.getLRPersistenceId());
1626    }
1627
1628    //3. get all LODADED documents
1629    //--Iterator it = corp.iterator();
1630    Iterator it = dbCorpus.getLoadedDocuments().iterator();
1631//Out.prln("loaded docs = ["+dbCorpus.getLoadedDocuments().size()+"]");
1632    List newlyAddedDocs = dbCorpus.getAddedDocuments();
1633
1634    while (it.hasNext()) {
1635      Document dbDoc = (Document)it.next();
1636      //note - document may be NULL which means it was not loaded (load on demand)
1637      //just ignore it then
1638      if (null == dbDoc) {
1639        continue;
1640      }
1641
1642      //adopt/sync?
1643      if (null == dbDoc.getLRPersistenceId()) {
1644        //doc was never adopted, adopt it
1645
1646        //3.1 remove the transient doc from the corpus
1647        it.remove();
1648
1649        //3.2 get the security info for the corpus
1650        SecurityInfo si = getSecurityInfo(corp);
1651
1652
1653        Document adoptedDoc = null;
1654        try {
1655          //3.3. adopt the doc with the sec info
1656//System.out.println("adopting ["+dbDoc.getName()+"] ...");
1657          //don't open a new transaction, since sync() already has opended one
1658          adoptedDoc = (Document)_adopt(dbDoc,si,true);
1659
1660          //3.4. add doc to corpus in DB
1661          addDocumentToCorpus((Long)adoptedDoc.getLRPersistenceId(),
1662                              (Long)corp.getLRPersistenceId());
1663        }
1664        catch(SecurityException se) {
1665          throw new PersistenceException(se);
1666        }
1667
1668        //3.5 add back to corpus the new DatabaseDocument
1669        corp.add(adoptedDoc);
1670      }
1671      else {
1672        //don't open a new transaction, the sync() called for corpus has already
1673        //opened one
1674        try {
1675          _sync(dbDoc,true);
1676
1677          // let the world know about it
1678          fireResourceWritten( new DatastoreEvent(this,
1679                                                  DatastoreEvent.RESOURCE_WRITTEN,
1680                                                  dbDoc,
1681                                                  dbDoc.getLRPersistenceId()
1682                                                  )
1683                              );
1684
1685          //if the document is form the same DS but did not belong to the corpus add it now
1686          //BUT ONLY if it's newly added - i.e. do nothing if the document already belongs to the
1687          //corpus and this is reflected in the database
1688          if (newlyAddedDocs.contains(dbDoc.getLRPersistenceId())) {
1689//Out.pr("A");
1690            addDocumentToCorpus( (Long) dbDoc.getLRPersistenceId(),
1691                                (Long) corp.getLRPersistenceId());
1692          }
1693          else {
1694//Out.pr("I");
1695          }
1696        }
1697        catch(SecurityException se) {
1698          gate.util.Err.prln("document cannot be synced: ["+se.getMessage()+"]");
1699        }
1700      }
1701    }
1702  }
1703
1704  /** helper for sync() - saves a Document in the database */
1705  /** helper for sync() - saves a Document in the database */
1706  protected void syncDocument(Document doc)
1707    throws PersistenceException, SecurityException {
1708
1709    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1710    Assert.assertTrue(doc.getLRPersistenceId() instanceof Long);
1711
1712    Long lrID = (Long)doc.getLRPersistenceId();
1713    EventAwareLanguageResource dbDoc = (EventAwareLanguageResource)doc;
1714    //1. sync LR
1715    // only name can be changed here
1716    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
1717      _syncLR(doc);
1718    }
1719
1720    //2. sync Document
1721    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_MAIN)) {
1722      _syncDocumentHeader(doc);
1723    }
1724
1725    //3. [optional] sync Content
1726    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_CONTENT)) {
1727      _syncDocumentContent(doc);
1728    }
1729
1730    //4. [optional] sync Features
1731    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
1732      _syncFeatures(doc);
1733    }
1734
1735    //5. [optional] delete from DB named sets that were removed from the document
1736    Collection removedSets = ((EventAwareDocument)dbDoc).getRemovedAnnotationSets();
1737    Collection addedSets = ((EventAwareDocument)dbDoc).getAddedAnnotationSets();
1738    if (false == removedSets.isEmpty() || false == addedSets.isEmpty()) {
1739      _syncAnnotationSets(doc,removedSets,addedSets);
1740    }
1741
1742    //6. [optional] sync Annotations
1743    _syncAnnotations(doc);
1744  }
1745
1746
1747  /**
1748   *  helper for sync()
1749   *  NEVER call directly
1750   */
1751  protected abstract void _syncLR(LanguageResource lr)
1752    throws PersistenceException,SecurityException;
1753
1754  /** helper for sync() - never call directly */
1755  protected abstract void _syncDocumentHeader(Document doc)
1756    throws PersistenceException;
1757
1758  /** helper for sync() - never call directly */
1759  protected abstract void _syncDocumentContent(Document doc)
1760    throws PersistenceException;
1761
1762  /** helper for sync() - never call directly */
1763  protected abstract void _syncFeatures(LanguageResource lr)
1764    throws PersistenceException;
1765
1766  /** helper for sync() - never call directly */
1767  protected void _syncAnnotationSets(Document doc,Collection removedSets,Collection addedSets)
1768    throws PersistenceException {
1769
1770    //0. preconditions
1771    Assert.assertNotNull(doc);
1772    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1773    Assert.assertNotNull(doc.getLRPersistenceId());
1774    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
1775                      this.getDatabaseID());
1776    Assert.assertNotNull(removedSets);
1777    Assert.assertNotNull(addedSets);
1778
1779    Long lrID = (Long)doc.getLRPersistenceId();
1780
1781    //1. delete from DB removed a-sets
1782    PreparedStatement stmt = null;
1783
1784    try {
1785
1786      if (this.dbType == DBHelper.ORACLE_DB) {
1787        stmt = this.jdbcConn.prepareCall("{ call "+this.dbSchema+"persist.delete_annotation_set(?,?) }");
1788      }
1789      else if (this.dbType == DBHelper.POSTGRES_DB) {
1790        stmt = this.jdbcConn.prepareStatement("select persist_delete_annotation_set(?,?)");
1791      }
1792      else {
1793        Assert.fail();
1794      }
1795
1796      Iterator it = removedSets.iterator();
1797      while (it.hasNext()) {
1798        String setName = (String)it.next();
1799        stmt.setLong(1,lrID.longValue());
1800        stmt.setString(2,setName);
1801        stmt.execute();
1802      }
1803    }
1804    catch(SQLException sqle) {
1805      throw new PersistenceException("can't remove annotation set from DB: ["+ sqle.getMessage()+"]");
1806    }
1807    finally {
1808      DBHelper.cleanup(stmt);
1809    }
1810
1811    //2. create in DB new a-sets
1812    Iterator it = addedSets.iterator();
1813    while (it.hasNext()) {
1814      String setName = (String)it.next();
1815      AnnotationSet aset = doc.getAnnotations(setName);
1816
1817      Assert.assertNotNull(aset);
1818      Assert.assertTrue(aset instanceof DatabaseAnnotationSetImpl);
1819
1820      createAnnotationSet(lrID,aset);
1821    }
1822  }
1823
1824
1825  /** helper for sync() - never call directly */
1826  protected void _syncAnnotations(Document doc)
1827    throws PersistenceException {
1828
1829    //0. preconditions
1830    Assert.assertNotNull(doc);
1831    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1832    Assert.assertNotNull(doc.getLRPersistenceId());
1833    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
1834                      this.getDatabaseID());
1835
1836
1837    EventAwareDocument ead = (EventAwareDocument)doc;
1838    //1. get the sets read from the DB for this document
1839    //chnaged annotations can occur only in such sets
1840    Collection loadedSets = ead.getLoadedAnnotationSets();
1841
1842    Iterator it = loadedSets.iterator();
1843    while (it.hasNext()) {
1844      AnnotationSet as = (AnnotationSet)it.next();
1845      //check that this set is neither NEW nor DELETED
1846      //they should be already synced
1847      if (ead.getAddedAnnotationSets().contains(as.getName()) ||
1848          ead.getRemovedAnnotationSets().contains(as.getName())) {
1849        //oops, ignore it
1850        continue;
1851      }
1852
1853      EventAwareAnnotationSet eas = (EventAwareAnnotationSet)as;
1854      Assert.assertNotNull(as);
1855
1856      Collection anns = null;
1857      anns = eas.getAddedAnnotations();
1858      Assert.assertNotNull(anns);
1859      if (anns.size()>0) {
1860        _syncAddedAnnotations(doc,as,anns);
1861      }
1862
1863      anns = eas.getRemovedAnnotations();
1864      Assert.assertNotNull(anns);
1865      if (anns.size()>0) {
1866        _syncRemovedAnnotations(doc,as,anns);
1867      }
1868
1869      anns = eas.getChangedAnnotations();
1870      Assert.assertNotNull(anns);
1871      if (anns.size()>0) {
1872        _syncChangedAnnotations(doc,as,anns);
1873      }
1874    }
1875  }
1876
1877  /** helper for sync() - never call directly */
1878  protected void _syncAddedAnnotations(Document doc, AnnotationSet as, Collection changes)
1879    throws PersistenceException {
1880
1881    //0.preconditions
1882    Assert.assertNotNull(doc);
1883    Assert.assertNotNull(as);
1884    Assert.assertNotNull(changes);
1885    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
1886    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
1887    Assert.assertTrue(changes.size() > 0);
1888
1889
1890    PreparedStatement pstmt = null;
1891    ResultSet rs = null;
1892    CallableStatement cstmt = null;
1893    Long lrID = (Long)doc.getLRPersistenceId();
1894    Long asetID = null;
1895
1896    try {
1897      //1. get the a-set ID in the database
1898      String sql = " select as_id  " +
1899                   " from  "+this.dbSchema+"v_annotation_set " +
1900                   " where  lr_id = ? ";
1901      //do we have aset name?
1902      String clause = null;
1903      String name = as.getName();
1904      if (null != name) {
1905        clause =   "        and as_name = ? ";
1906      }
1907      else {
1908        clause =   "        and as_name is null ";
1909      }
1910      sql = sql + clause;
1911
1912      pstmt = this.jdbcConn.prepareStatement(sql);
1913      pstmt.setLong(1,lrID.longValue());
1914      if (null != name) {
1915        pstmt.setString(2,name);
1916      }
1917      pstmt.execute();
1918      rs = pstmt.getResultSet();
1919
1920      if (rs.next()) {
1921        asetID = new Long(rs.getLong("as_id"));
1922      }
1923      else {
1924        throw new PersistenceException("cannot find annotation set with" +
1925                                      " name=["+name+"] , LRID=["+lrID+"] in database");
1926      }
1927
1928      //cleanup
1929      DBHelper.cleanup(rs);
1930      DBHelper.cleanup(pstmt);
1931
1932      //3. insert the new annotations from this set
1933
1934      //3.1. prepare call
1935      if (this.dbType == DBHelper.ORACLE_DB) {
1936
1937        cstmt = this.jdbcConn.prepareCall(
1938                "{ call "+Gate.DB_OWNER+".persist.create_annotation(?,?,?,?,?,?,?,?,?) }");
1939
1940        Long annGlobalID = null;
1941        Iterator it = changes.iterator();
1942
1943        while (it.hasNext()) {
1944
1945          //3.2. insert annotation
1946          Annotation ann = (Annotation)it.next();
1947
1948          Node start = (Node)ann.getStartNode();
1949          Node end = (Node)ann.getEndNode();
1950          String type = ann.getType();
1951
1952          cstmt.setLong(1,lrID.longValue());
1953          cstmt.setLong(2,ann.getId().longValue());
1954          cstmt.setLong(3,asetID.longValue());
1955          cstmt.setLong(4,start.getId().longValue());
1956          cstmt.setLong(5,start.getOffset().longValue());
1957          cstmt.setLong(6,end.getId().longValue());
1958          cstmt.setLong(7,end.getOffset().longValue());
1959          cstmt.setString(8,type);
1960          cstmt.registerOutParameter(9,java.sql.Types.BIGINT);
1961
1962          cstmt.execute();
1963          annGlobalID = new Long(cstmt.getLong(9));
1964
1965          //3.3. set annotation features
1966          FeatureMap features = ann.getFeatures();
1967          Assert.assertNotNull(features);
1968
1969          if (this.dbType == DBHelper.ORACLE_DB) {
1970            createFeaturesBulk(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1971          }
1972          else if (this.dbType == DBHelper.POSTGRES_DB) {
1973            createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1974          }
1975          else {
1976            Assert.fail();
1977          }
1978        }
1979      }
1980      else if (this.dbType == DBHelper.POSTGRES_DB) {
1981
1982        sql = "select persist_create_annotation(?,?,?,?,?,?,?,?)";
1983        pstmt = this.jdbcConn.prepareStatement(sql);
1984
1985        Long annGlobalID = null;
1986        Iterator it = changes.iterator();
1987
1988        while (it.hasNext()) {
1989
1990          //3.2. insert annotation
1991          Annotation ann = (Annotation)it.next();
1992
1993          Node start = (Node)ann.getStartNode();
1994          Node end = (Node)ann.getEndNode();
1995          String type = ann.getType();
1996
1997          pstmt.setLong(1,lrID.longValue());
1998          pstmt.setLong(2,ann.getId().longValue());
1999          pstmt.setLong(3,asetID.longValue());
2000          pstmt.setLong(4,start.getId().longValue());
2001          pstmt.setLong(5,start.getOffset().longValue());
2002          pstmt.setLong(6,end.getId().longValue());
2003          pstmt.setLong(7,end.getOffset().longValue());
2004          pstmt.setString(8,type);
2005          pstmt.execute();
2006
2007          rs = pstmt.getResultSet();
2008
2009          if (false == rs.next()) {
2010            throw new PersistenceException("empty result set");
2011          }
2012          annGlobalID = new Long(rs.getLong(1));
2013
2014          //3.3. set annotation features
2015          FeatureMap features = ann.getFeatures();
2016          Assert.assertNotNull(features);
2017          createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
2018        }
2019      }
2020
2021      else {
2022        throw new IllegalArgumentException();
2023      }
2024
2025    }
2026    catch(SQLException sqle) {
2027      throw new PersistenceException("can't add annotations in DB : ["+
2028                                      sqle.getMessage()+"]");
2029    }
2030    finally {
2031      DBHelper.cleanup(rs);
2032      DBHelper.cleanup(pstmt);
2033      DBHelper.cleanup(cstmt);
2034    }
2035  }
2036
2037  /** helper for sync() - never call directly */
2038  protected void _syncRemovedAnnotations(Document doc,AnnotationSet as, Collection changes)
2039    throws PersistenceException {
2040
2041    //0.preconditions
2042    Assert.assertNotNull(doc);
2043    Assert.assertNotNull(as);
2044    Assert.assertNotNull(changes);
2045    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2046    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
2047    Assert.assertTrue(changes.size() > 0);
2048
2049
2050    PreparedStatement pstmt = null;
2051    ResultSet rs = null;
2052    Long lrID = (Long)doc.getLRPersistenceId();
2053    Long docID = null;
2054    Long asetID = null;
2055
2056    try {
2057      //1. get the a-set ID in the database
2058      String sql = " select as_id,  " +
2059                   "        as_doc_id " +
2060                   " from  "+this.dbSchema+"v_annotation_set " +
2061                   " where  lr_id = ? ";
2062      //do we have aset name?
2063      String clause = null;
2064      String name = as.getName();
2065      if (null != name) {
2066        clause =   "        and as_name = ? ";
2067      }
2068      else {
2069        clause =   "        and as_name is null ";
2070      }
2071      sql = sql + clause;
2072
2073      pstmt = this.jdbcConn.prepareStatement(sql);
2074      pstmt.setLong(1,lrID.longValue());
2075      if (null != name) {
2076        pstmt.setString(2,name);
2077      }
2078      pstmt.execute();
2079      rs = pstmt.getResultSet();
2080
2081      if (rs.next()) {
2082        asetID = new Long(rs.getLong("as_id"));
2083        docID = new Long(rs.getLong("as_doc_id"));
2084      }
2085      else {
2086        throw new PersistenceException("cannot find annotation set with" +
2087                                      " name=["+name+"] , LRID=["+lrID+"] in database");
2088      }
2089
2090      //3. delete the removed annotations from this set
2091
2092      //cleanup
2093      DBHelper.cleanup(rs);
2094      DBHelper.cleanup(pstmt);
2095
2096      //3.1. prepare call
2097
2098      if (this.dbType == DBHelper.ORACLE_DB) {
2099        pstmt = this.jdbcConn.prepareCall("{ call "+this.dbSchema+"persist.delete_annotation(?,?) }");
2100      }
2101      else if (this.dbType == DBHelper.POSTGRES_DB) {
2102        pstmt = this.jdbcConn.prepareStatement("select persist_delete_annotation(?,?)");
2103      }
2104      else {
2105        throw new IllegalArgumentException();
2106      }
2107
2108      Iterator it = changes.iterator();
2109
2110      while (it.hasNext()) {
2111
2112        //3.2. insert annotation
2113        Annotation ann = (Annotation)it.next();
2114
2115        pstmt.setLong(1,docID.longValue()); //annotations are linked with documents, not LRs!
2116        pstmt.setLong(2,ann.getId().longValue());
2117        pstmt.execute();
2118      }
2119    }
2120    catch(SQLException sqle) {
2121      throw new PersistenceException("can't delete annotations in DB : ["+
2122                                      sqle.getMessage()+"]");
2123    }
2124    finally {
2125      DBHelper.cleanup(rs);
2126      DBHelper.cleanup(pstmt);
2127    }
2128  }
2129
2130
2131  /** helper for sync() - never call directly */
2132  protected void _syncChangedAnnotations(Document doc,AnnotationSet as, Collection changes)
2133    throws PersistenceException {
2134
2135    //technically this approach sux
2136    //at least it works
2137
2138    //1. delete
2139    _syncRemovedAnnotations(doc,as,changes);
2140    //2. recreate
2141    _syncAddedAnnotations(doc,as,changes);
2142  }
2143
2144  /**
2145   * Get a resource from the persistent store.
2146   * <B>Don't use this method - use Factory.createResource with
2147   * DataStore and DataStoreInstanceId parameters set instead.</B>
2148   */
2149  public LanguageResource getLr(String lrClassName, Object lrPersistenceId)
2150  throws PersistenceException,SecurityException {
2151
2152    LanguageResource result = null;
2153
2154    //0. preconditions
2155    Assert.assertNotNull(lrPersistenceId);
2156
2157    //1. check session
2158    if (null == this.session) {
2159      throw new SecurityException("session not set");
2160    }
2161
2162    if (false == this.ac.isValidSession(this.session)) {
2163      throw new SecurityException("invalid session supplied");
2164    }
2165
2166    //2. check permissions
2167    if (false == canReadLR(lrPersistenceId)) {
2168      throw new SecurityException("insufficient privileges");
2169    }
2170
2171    //3. get resource from DB
2172    if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
2173      result = readDocument(lrPersistenceId);
2174      Assert.assertTrue(result instanceof DatabaseDocumentImpl);
2175    }
2176    else if (lrClassName.equals(DBHelper.CORPUS_CLASS)) {
2177      result = readCorpus(lrPersistenceId);
2178      Assert.assertTrue(result instanceof DatabaseCorpusImpl);
2179    }
2180    else {
2181      throw new IllegalArgumentException("resource class should be either Document or Corpus");
2182    }
2183
2184    //4. postconditions
2185    Assert.assertNotNull(result.getDataStore());
2186    Assert.assertTrue(result.getDataStore() instanceof DatabaseDataStore);
2187    Assert.assertNotNull(result.getLRPersistenceId());
2188
2189    //5. register the read doc as listener for sync events
2190    addDatastoreListener((DatastoreListener)result);
2191
2192    //6. add the resource to the list of dependent resources - i.e. the ones that the
2193    //data store should take care upon closing [and call sync()]
2194    this.dependentResources.add(result);
2195
2196    //7. done
2197    return result;
2198  }
2199
2200  /** helper method for getLR - reads LR of type Document */
2201  private DatabaseDocumentImpl readDocument(Object lrPersistenceId)
2202    throws PersistenceException {
2203
2204    //0. preconditions
2205    Assert.assertNotNull(lrPersistenceId);
2206
2207    if (false == lrPersistenceId instanceof Long) {
2208      throw new IllegalArgumentException();
2209    }
2210
2211    // 1. dummy document to be initialized
2212    DatabaseDocumentImpl result = new DatabaseDocumentImpl(this.jdbcConn);
2213
2214    PreparedStatement pstmt = null;
2215    ResultSet rs = null;
2216
2217    //3. read from DB
2218    try {
2219      String sql = " select lr_name, " +
2220                   "        lrtp_type, " +
2221                   "        lr_id, " +
2222                   "        lr_parent_id, " +
2223                   "        doc_id, " +
2224                   "        doc_url, " +
2225                   "        doc_start, " +
2226                   "        doc_end, " +
2227                   "        doc_is_markup_aware " +
2228                   " from  "+this.dbSchema+"v_document " +
2229                   " where  lr_id = ? ";
2230
2231      pstmt = this.jdbcConn.prepareStatement(sql);
2232      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2233      pstmt.execute();
2234      rs = pstmt.getResultSet();
2235
2236      if (false == rs.next()) {
2237        //ooops mo data found
2238        throw new PersistenceException("Invalid LR ID supplied - no data found");
2239      }
2240
2241      //4. fill data
2242
2243      //4.0 name
2244      String lrName = rs.getString("lr_name");
2245      Assert.assertNotNull(lrName);
2246      result.setName(lrName);
2247
2248      //4.1 parent
2249      Long parentID = null;
2250      long parent_id = rs.getLong("lr_parent_id");
2251      if (false == rs.wasNull()) {
2252        parentID = new Long(parent_id);
2253
2254        //read parent resource
2255        LanguageResource parentLR = this.getLr(DBHelper.DOCUMENT_CLASS,parentID);
2256        Assert.assertNotNull(parentLR);
2257        Assert.assertTrue(parentLR instanceof DatabaseDocumentImpl);
2258
2259        result.setParent(parentLR);
2260      }
2261
2262
2263      //4.2. markup aware
2264      if (this.dbType == DBHelper.ORACLE_DB) {
2265        long markup = rs.getLong("doc_is_markup_aware");
2266        Assert.assertTrue(markup == DBHelper.FALSE || markup == DBHelper.TRUE);
2267        if (markup == DBHelper.FALSE) {
2268          result.setMarkupAware(Boolean.FALSE);
2269        }
2270        else {
2271          result.setMarkupAware(Boolean.TRUE);
2272
2273        }
2274      }
2275      else if (this.dbType == DBHelper.POSTGRES_DB) {
2276        boolean markup = rs.getBoolean("doc_is_markup_aware");
2277        result.setMarkupAware(new Boolean(markup));
2278      }
2279      else {
2280        throw new IllegalArgumentException();
2281      }
2282
2283
2284      //4.3 datastore
2285      result.setDataStore(this);
2286
2287      //4.4. persist ID
2288      Long persistID = new Long(rs.getLong("lr_id"));
2289      result.setLRPersistenceId(persistID);
2290
2291      //4.5  source url
2292      String url = rs.getString("doc_url");
2293      if(url != null && url.length() > 0) result.setSourceUrl(new URL(url));
2294
2295      //4.6. start offset
2296      Long start = null;
2297      long longVal = rs.getLong("doc_start");
2298      //null?
2299      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2300      if (false == rs.wasNull()) {
2301        start = new Long(longVal);
2302      }
2303      result.setSourceUrlStartOffset(start);
2304//      initData.put("DOC_SOURCE_URL_START",start);
2305
2306      //4.7. end offset
2307      Long end = null;
2308      longVal = rs.getLong("doc_end");
2309      //null?
2310      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2311      if (false == rs.wasNull()) {
2312        end = new Long(longVal);
2313      }
2314      result.setSourceUrlEndOffset(end);
2315//      initData.put("DOC_SOURCE_URL_END",end);
2316
2317      //4.8 features
2318      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_DOCUMENT);
2319      result.setFeatures(features);
2320      //initData.put("DOC_FEATURES",features);
2321
2322      //4.9 set the nextAnnotationID correctly
2323      long doc_id = rs.getLong("doc_id");
2324
2325      //cleanup
2326      DBHelper.cleanup(rs);
2327      DBHelper.cleanup(pstmt);
2328
2329      sql = " select  max(ann_local_id),'ann_id'" +
2330            " from "+this.dbSchema+"t_annotation " +
2331            " where ann_doc_id = ?" +
2332            " union " +
2333            " select max(node_local_id),'node_id' " +
2334            " from "+this.dbSchema+"t_node " +
2335            " where node_doc_id = ?";
2336
2337      pstmt = this.jdbcConn.prepareStatement(sql);
2338      pstmt.setLong(1,doc_id);
2339      pstmt.setLong(2,doc_id);
2340      pstmt.execute();
2341      rs = pstmt.getResultSet();
2342
2343      int maxAnnID = 0 , maxNodeID = 0;
2344      //ann id
2345      if (false == rs.next()) {
2346        //ooops no data found
2347        throw new PersistenceException("Invalid LR ID supplied - no data found");
2348      }
2349      if (rs.getString(2).equals("ann_id"))
2350        maxAnnID = rs.getInt(1);
2351      else
2352        maxNodeID = rs.getInt(1);
2353
2354      if (false == rs.next()) {
2355        //ooops no data found
2356        throw new PersistenceException("Invalid LR ID supplied - no data found");
2357      }
2358      if (rs.getString(2).equals("node_id"))
2359        maxNodeID = rs.getInt(1);
2360      else
2361        maxAnnID = rs.getInt(1);
2362
2363      result.setNextNodeId(maxNodeID+1);
2364//      initData.put("DOC_NEXT_NODE_ID",new Integer(maxNodeID+1));
2365      result.setNextAnnotationId(maxAnnID+1);
2366//      initData.put("DOC_NEXT_ANN_ID",new Integer(maxAnnID+1));
2367
2368
2369//      params.put("initData__$$__", initData);
2370//      try {
2371        //here we create the persistent LR via Factory, so it's registered
2372        //in GATE
2373//        result = (DatabaseDocumentImpl)Factory.createResource("gate.corpora.DatabaseDocumentImpl", params);
2374//      }
2375//      catch (gate.creole.ResourceInstantiationException ex) {
2376//        throw new GateRuntimeException(ex.getMessage());
2377//      }
2378    }
2379    catch(SQLException sqle) {
2380      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2381    }
2382    catch(Exception e) {
2383      throw new PersistenceException(e);
2384    }
2385    finally {
2386      DBHelper.cleanup(rs);
2387      DBHelper.cleanup(pstmt);
2388    }
2389
2390    return result;
2391  }
2392
2393
2394  /**
2395   *  helper method for getLR - reads LR of type Corpus
2396   */
2397  private DatabaseCorpusImpl readCorpus(Object lrPersistenceId)
2398    throws PersistenceException {
2399
2400    //0. preconditions
2401    Assert.assertNotNull(lrPersistenceId);
2402
2403    if (false == lrPersistenceId instanceof Long) {
2404      throw new IllegalArgumentException();
2405    }
2406
2407    //3. read from DB
2408    PreparedStatement pstmt = null;
2409    ResultSet rs = null;
2410    DatabaseCorpusImpl result = null;
2411
2412    try {
2413      String sql = " select lr_name " +
2414                   " from  "+this.dbSchema+"t_lang_resource " +
2415                   " where  lr_id = ? ";
2416      pstmt = this.jdbcConn.prepareStatement(sql);
2417      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2418      pstmt.execute();
2419      rs = pstmt.getResultSet();
2420
2421      if (false == rs.next()) {
2422        //ooops mo data found
2423        throw new PersistenceException("Invalid LR ID supplied - no data found");
2424      }
2425
2426      //4. fill data
2427
2428      //4.1 name
2429      String lrName = rs.getString("lr_name");
2430      Assert.assertNotNull(lrName);
2431
2432      //4.8 features
2433      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_CORPUS);
2434
2435      //4.9 cleanup
2436      DBHelper.cleanup(rs);
2437      DBHelper.cleanup(pstmt);
2438
2439      sql = " select lr_id ," +
2440            "         lr_name " +
2441            " from "+this.dbSchema+"t_document        doc, " +
2442            "      "+this.dbSchema+"t_lang_resource   lr, " +
2443            "      "+this.dbSchema+"t_corpus_document corpdoc, " +
2444            "      "+this.dbSchema+"t_corpus          corp " +
2445            " where lr.lr_id = doc.doc_lr_id " +
2446            "       and doc.doc_id = corpdoc.cd_doc_id " +
2447            "       and corpdoc.cd_corp_id = corp.corp_id " +
2448            "       and corp_lr_id = ? ";
2449      pstmt = this.jdbcConn.prepareStatement(sql);
2450      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2451      pstmt.execute();
2452      rs = pstmt.getResultSet();
2453
2454      Vector documentData = new Vector();
2455      while (rs.next()) {
2456        Long docLRID = new Long(rs.getLong("lr_id"));
2457        String docName = rs.getString("lr_name");
2458        documentData.add(new DocumentData(docName, docLRID));
2459      }
2460      DBHelper.cleanup(rs);
2461      DBHelper.cleanup(pstmt);
2462
2463      result = new DatabaseCorpusImpl(lrName,
2464                                      this,
2465                                      (Long)lrPersistenceId,
2466                                      features,
2467                                      documentData);
2468    }
2469    catch(SQLException sqle) {
2470      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2471    }
2472    catch(Exception e) {
2473      throw new PersistenceException(e);
2474    }
2475    finally {
2476      DBHelper.cleanup(rs);
2477      DBHelper.cleanup(pstmt);
2478    }
2479
2480    return result;
2481  }
2482
2483  /**
2484   *  reads the features of an entity
2485   *  entities are of type LR or Annotation
2486   */
2487  protected abstract FeatureMap readFeatures(Long entityID, int entityType)
2488    throws PersistenceException;
2489
2490  /**
2491   *  helper method for delete()
2492   *  never call it directly beause proper events will not be fired
2493   */
2494  protected abstract void deleteDocument(Long lrId)
2495    throws PersistenceException;
2496
2497  /**
2498   *  helper method for delete()
2499   *  never call it directly beause proper events will not be fired
2500   */
2501  protected abstract void deleteCorpus(Long lrId)
2502    throws PersistenceException;
2503
2504  /**
2505   *   unloads a LR from the GUI
2506   */
2507  protected void unloadLR(Long lrID)
2508  throws GateException{
2509
2510    //0. preconfitions
2511    Assert.assertNotNull(lrID);
2512
2513    //1. get all LRs in the system
2514    List resources = Gate.getCreoleRegister().getAllInstances("gate.LanguageResource");
2515
2516    Iterator it = resources.iterator();
2517    while (it.hasNext()) {
2518      LanguageResource lr = (LanguageResource)it.next();
2519      if (lrID.equals(lr.getLRPersistenceId()) &&
2520          this.equals(lr.getDataStore())) {
2521        //found it - unload it
2522        Factory.deleteResource(lr);
2523        break;
2524      }
2525    }
2526  }
2527
2528  /** helper for sync() - never call directly */
2529  protected abstract void _syncRemovedDocumentsFromCorpus(List docLRIDs, Long corpLRID)
2530    throws PersistenceException;
2531
2532  /**
2533   *   adds document to corpus in the database
2534   *   if the document is already part of the corpus nothing
2535   *   changes
2536   */
2537  protected abstract void addDocumentToCorpus(Long docID,Long corpID)
2538  throws PersistenceException,SecurityException;
2539
2540
2541}
2542