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