1   /*
2    *  DatabaseDocumentImpl.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, 16/Oct/2001
12   *
13   *  $Id: DatabaseDocumentImpl.java,v 1.42 2001/11/29 09:11:38 marin Exp $
14   */
15  
16  package gate.corpora;
17  
18  
19  import java.sql.*;
20  import java.io.*;
21  import java.util.*;
22  import java.net.*;
23  
24  import junit.framework.*;
25  
26  import gate.*;
27  import gate.util.*;
28  import gate.persist.*;
29  import gate.annotation.*;
30  import gate.creole.*;
31  import gate.event.*;
32  
33  public class DatabaseDocumentImpl extends DocumentImpl
34                                    implements  DatastoreListener,
35                                                Document,
36                                                EventAwareDocument {
37  
38    private static final boolean DEBUG = false;
39  
40    private boolean     isContentRead;
41    private Object      contentLock;
42    private Connection  jdbcConn;
43  
44    private boolean     contentChanged;
45    private boolean     featuresChanged;
46    private boolean     nameChanged;
47    private boolean     documentChanged;
48  
49    private Collection  removedAnotationSets;
50    private Collection  addedAnotationSets;
51  
52    //this one should be the same as the values returned
53    //in persist.get_id_lot PL/SQL package
54    //it sux actually
55    private static final int SEQUENCE_POOL_SIZE = 10;
56  
57    private Integer sequencePool[];
58    private int poolMarker;
59  
60    /**
61     * The listener for the events coming from the features.
62     */
63    protected EventsHandler eventHandler;
64  
65  
66    public DatabaseDocumentImpl(Connection conn) {
67  
68      //super();
69      contentLock = new Object();
70  
71      this.namedAnnotSets = new HashMap();
72  //    this.defaultAnnots = new DatabaseAnnotationSetImpl(this);
73  
74      this.isContentRead = false;
75      this.jdbcConn = conn;
76  
77      this.contentChanged = false;
78      this.featuresChanged = false;
79      this.nameChanged = false;
80      this.documentChanged = false;
81  
82      this.removedAnotationSets = new Vector();
83      this.addedAnotationSets = new Vector();
84  
85      sequencePool = new Integer[this.SEQUENCE_POOL_SIZE];
86      poolMarker = this.SEQUENCE_POOL_SIZE;
87    }
88  
89    public DatabaseDocumentImpl(Connection _conn,
90                                String _name,
91                                DatabaseDataStore _ds,
92                                Long _persistenceID,
93                                DocumentContent _content,
94                                FeatureMap _features,
95                                Boolean _isMarkupAware,
96                                URL _sourceURL,
97                                Long _urlStartOffset,
98                                Long _urlEndOffset,
99                                AnnotationSet _default,
100                               Map _named) {
101 
102     //this.jdbcConn =  _conn;
103     this(_conn);
104 
105     this.name = _name;
106     this.dataStore = _ds;
107     this.lrPersistentId = _persistenceID;
108     this.content = _content;
109     this.isContentRead = true;
110     this.features = _features;
111     this.markupAware = _isMarkupAware;
112     this.sourceUrl = _sourceURL;
113     this.sourceUrlStartOffset = _urlStartOffset;
114     this.sourceUrlEndOffset = _urlEndOffset;
115 
116     //annotations
117     //1. default
118     _setAnnotations(null,_default);
119 
120     //2. named (if any)
121     if (null != _named) {
122       Iterator itNamed = _named.values().iterator();
123       while (itNamed.hasNext()){
124         AnnotationSet currSet = (AnnotationSet)itNamed.next();
125         //add them all to the DBAnnotationSet
126         _setAnnotations(currSet.getName(),currSet);
127       }
128     }
129 
130     //3. add the listeners for the features
131     if (eventHandler == null)
132       eventHandler = new EventsHandler();
133     this.features.addFeatureMapListener(eventHandler);
134 
135     //4. add self as listener for the data store, so that we'll know when the DS is
136     //synced and we'll clear the isXXXChanged flags
137     this.dataStore.addDatastoreListener(this);
138   }
139 
140   /** The content of the document: a String for text; MPEG for video; etc. */
141   public DocumentContent getContent() {
142 
143     //1. assert that no one is reading from DB now
144     synchronized(this.contentLock) {
145       if (false == this.isContentRead) {
146         _readContent();
147         this.isContentRead = true;
148       }
149     }
150 
151     //return content
152     return super.getContent();
153   }
154 
155   private void _readContent() {
156 
157     //preconditions
158     if (null == getLRPersistenceId()) {
159       throw new GateRuntimeException("can't construct a DatabaseDocument - not associated " +
160                                     " with any data store");
161     }
162 
163     if (false == getLRPersistenceId() instanceof Long) {
164       throw new GateRuntimeException("can't construct a DatabaseDocument -  " +
165                                       " invalid persistence ID");
166     }
167 
168     Long lrID = (Long)getLRPersistenceId();
169     //0. preconditions
170     Assert.assertNotNull(lrID);
171     Assert.assertTrue(false == this.isContentRead);
172     Assert.assertNotNull(this.content);
173 
174     //1. read from DB
175     PreparedStatement pstmt = null;
176     ResultSet rs = null;
177 
178     try {
179       String sql = " select v1.enc_name, " +
180                    "        v1.dc_character_content, " +
181                    "        v1.dc_binary_content, " +
182                    "        v1.dc_content_type " +
183                    " from  "+Gate.DB_OWNER+".v_content v1 " +
184                    " where  v1.lr_id = ? ";
185 
186       pstmt = this.jdbcConn.prepareStatement(sql);
187       pstmt.setLong(1,lrID.longValue());
188       pstmt.execute();
189       rs = pstmt.getResultSet();
190 
191       rs.next();
192 
193       String encoding = rs.getString(1);
194       Clob   clb = rs.getClob(2);
195       Blob   blb = rs.getBlob(3);
196       long   contentType = rs.getLong(4);
197 
198       Assert.assertTrue(DBHelper.CHARACTER_CONTENT == contentType);
199 
200       StringBuffer buff = new StringBuffer();
201       OracleDataStore.readCLOB(clb,buff);
202 
203       //2. set data members that were not previously initialized
204       this.content = new DocumentContentImpl(buff.toString());
205       this.encoding = encoding;
206     }
207     catch(SQLException sqle) {
208       throw new SynchronisationException("can't read content from DB: ["+ sqle.getMessage()+"]");
209     }
210     catch(IOException ioe) {
211       throw new SynchronisationException(ioe);
212     }
213     finally {
214       try {
215         DBHelper.cleanup(rs);
216         DBHelper.cleanup(pstmt);
217       }
218       catch(PersistenceException pe) {
219         throw new SynchronisationException("JDBC error: ["+ pe.getMessage()+"]");
220       }
221     }
222   }
223 
224 
225   /** Get the encoding of the document content source */
226   public String getEncoding() {
227 
228     //1. assert that no one is reading from DB now
229     synchronized(this.contentLock) {
230       if (false == this.isContentRead) {
231         _readContent();
232 
233         this.isContentRead = true;
234       }
235     }
236 
237     return super.getEncoding();
238   }
239 
240   /** Returns a map with the named annotation sets. It returns <code>null</code>
241    *  if no named annotaton set exists. */
242   public Map getNamedAnnotationSets() {
243 
244     Vector annNames = new Vector();
245 
246     PreparedStatement pstmt = null;
247     ResultSet rs = null;
248 
249     //1. get the names of all sets
250     try {
251       String sql = " select as_name " +
252                    " from  "+Gate.DB_OWNER+".v_annotation_set " +
253                    " where  lr_id = ? " +
254                    "  and as_name is not null";
255 
256       pstmt = this.jdbcConn.prepareStatement(sql);
257       pstmt.setLong(1,((Long)this.lrPersistentId).longValue());
258       pstmt.execute();
259       rs = pstmt.getResultSet();
260 
261       while (rs.next()) {
262         annNames.add(rs.getString("as_name"));
263       }
264     }
265     catch(SQLException sqle) {
266       throw new SynchronisationException("can't get named annotatios: ["+ sqle.getMessage()+"]");
267     }
268     finally {
269       try {
270         DBHelper.cleanup(rs);
271         DBHelper.cleanup(pstmt);
272       }
273       catch(PersistenceException pe) {
274         throw new SynchronisationException("JDBC error: ["+ pe.getMessage()+"]");
275       }
276     }
277 
278     //2. read annotations
279     for (int i=0; i< annNames.size(); i++) {
280       //delegate because of the data is already read getAnnotations() will just return
281       getAnnotations((String)annNames.elementAt(i));
282     }
283 
284     //3. delegate to the parent method
285     return super.getNamedAnnotationSets();
286 
287   } // getNamedAnnotationSets
288 
289 
290   /** Get the default set of annotations. The set is created if it
291     * doesn't exist yet.
292     */
293   public AnnotationSet getAnnotations() {
294 
295     //1. read from DB
296     _getAnnotations(null);
297 
298     //2. is there such set in the DB?
299     if (null == this.defaultAnnots) {
300       //create a DatabaseAnnotationSetImpl
301       //NOTE: we create the set and then delegate to the super mehtod, otherwise
302       //the super mehtod will create AnnotationSetImpl instead of DatabaseAnnotationSetImpl
303       //which will not work with DatabaseDocumentImpl
304       AnnotationSet aset = new DatabaseAnnotationSetImpl(this);
305 
306       //set internal member
307       this.defaultAnnots = aset;
308 
309       //3. fire events
310       fireAnnotationSetAdded(new DocumentEvent(this,
311                                                 DocumentEvent.ANNOTATION_SET_ADDED,
312                                                 null));
313     }
314 
315     //4. delegate
316     return super.getAnnotations();
317   } // getAnnotations()
318 
319 
320   /** Get a named set of annotations. Creates a new set if one with this
321     * name doesn't exist yet.
322     * If the provided name is null then it returns the default annotation set.
323     */
324   public AnnotationSet getAnnotations(String name) {
325 
326     //0. preconditions
327     Assert.assertNotNull(name);
328 
329     //1. read from DB if the set is there at all
330     _getAnnotations(name);
331 
332     //2. is there such set in the DB?
333     if (false == this.namedAnnotSets.keySet().contains(name)) {
334       //create a DatabaseAnnotationSetImpl
335       //NOTE: we create the set and then delegate to the super mehtod, otherwise
336       //the super mehtod will create AnnotationSetImpl instead of DatabaseAnnotationSetImpl
337       //which will not work with DatabaseDocumentImpl
338       AnnotationSet aset = new DatabaseAnnotationSetImpl(this,name);
339 
340       //add to internal collection
341       this.namedAnnotSets.put(name,aset);
342 
343       //add the set name to the list with the recently created sets
344       this.addedAnotationSets.add(name);
345 
346       //3. fire events
347       DocumentEvent evt = new DocumentEvent(this, DocumentEvent.ANNOTATION_SET_ADDED, name);
348       fireAnnotationSetAdded(evt);
349     }
350 
351     //3. delegate
352     return super.getAnnotations(name);
353   }
354 
355 
356   private void _getAnnotations(String name) {
357 
358     AnnotationSet as = null;
359 
360     //preconditions
361     if (null == getLRPersistenceId()) {
362       throw new GateRuntimeException("can't construct a DatabaseDocument - not associated " +
363                                     " with any data store");
364     }
365 
366     if (false == getLRPersistenceId() instanceof Long) {
367       throw new GateRuntimeException("can't construct a DatabaseDocument -  " +
368                                       " invalid persistence ID");
369     }
370 
371     //have we already read this set?
372 
373     if (null == name) {
374       //default set
375       if (this.defaultAnnots != null) {
376         //the default set is alredy read - do nothing
377         //super methods will take care
378         return;
379       }
380     }
381     else {
382       //named set
383       if (this.namedAnnotSets.containsKey(name)) {
384         //we've already read it - do nothing
385         //super methods will take care
386         return;
387       }
388     }
389 
390 
391     Long lrID = (Long)getLRPersistenceId();
392     Long asetID = null;
393     //0. preconditions
394     Assert.assertNotNull(lrID);
395 
396     //1. read a-set info
397     PreparedStatement pstmt = null;
398     ResultSet rs = null;
399     try {
400       String sql = " select as_id " +
401                    " from  "+Gate.DB_OWNER+".v_annotation_set " +
402                    " where  lr_id = ? ";
403       //do we have aset name?
404       String clause = null;
405       if (null != name) {
406         clause =   "        and as_name = ? ";
407       }
408       else {
409         clause =   "        and as_name is null ";
410       }
411       sql = sql + clause;
412 //System.out.println(sql);
413       pstmt = this.jdbcConn.prepareStatement(sql);
414         pstmt.setLong(1,lrID.longValue());
415         if (null != name) {
416           pstmt.setString(2,name);
417         }
418         pstmt.execute();
419         rs = pstmt.getResultSet();
420 
421         if (rs.next()) {
422           //ok, there is such aset in the DB
423           asetID = new Long(rs.getLong(1));
424         }
425         else {
426           //wow, there is no such aset, so create new ...
427           //... by delegating to the super method
428           return;
429         }
430 
431         //2. read annotation Features
432         HashMap featuresByAnnotationID = _readFeatures(asetID);
433 
434         //3. read annotations
435         AnnotationSetImpl transSet = new AnnotationSetImpl(this);
436 
437         try {
438           String sql1 = " select ann_local_id, " +
439                        "        at_name, " +
440                        "        start_offset, " +
441                        "        end_offset " +
442                        " from  "+Gate.DB_OWNER+".v_annotation  " +
443                        " where  asann_as_id = ? ";
444 
445         if (DEBUG) Out.println(">>>>> asetID=["+asetID+"]");
446 
447         pstmt = this.jdbcConn.prepareStatement(sql1);
448         pstmt.setLong(1,asetID.longValue());
449         pstmt.execute();
450         rs = pstmt.getResultSet();
451 
452         while (rs.next()) {
453           //1. read data memebers
454           Integer annID = new Integer(rs.getInt(1));
455           String type = rs.getString(2);
456           Long startOffset = new Long(rs.getLong(3));
457           Long endOffset = new Long(rs.getLong(4));
458 
459           if (DEBUG) Out.println("annID=["+annID+"]");
460           if (DEBUG) Out.println("start=["+startOffset+"]");
461           if (DEBUG) Out.println("end=["+endOffset+"]");
462 
463           //2. get the features
464           FeatureMap fm = (FeatureMap)featuresByAnnotationID.get(annID);
465           //fm may should NOT be null
466           if (null == fm) {
467             fm =  new SimpleFeatureMapImpl();
468           }
469 
470           //3. add to annotation set
471           transSet.add(annID,startOffset,endOffset,type,fm);
472         }//while
473         }//read the annotations
474         catch(SQLException sqle) {
475           throw new SynchronisationException("can't read content from DB: ["
476                                             + sqle.getMessage()+"]");
477         }
478         catch(InvalidOffsetException oe) {
479           throw new SynchronisationException(oe);
480         }
481         finally {
482           try {
483             DBHelper.cleanup(rs);
484             DBHelper.cleanup(pstmt);
485           }
486           catch(PersistenceException pe) {
487             throw new SynchronisationException("JDBC error: ["
488                                               + pe.getMessage()+"]");
489           }
490         }//finally
491 
492         //1.5, create a-set
493         if (null == name) {
494           as = new DatabaseAnnotationSetImpl(this, transSet);
495         }
496         else {
497           as = new DatabaseAnnotationSetImpl(this,name, transSet);
498         }
499 
500         //1.6 add the new a-set to the list of the a-sets read from the DB
501 //        this.loadedAnnotSets.add(as);
502 
503       }
504       catch(SQLException sqle) {
505         throw new SynchronisationException("can't read annotations from DB: ["+ sqle.getMessage()+"]");
506       }
507       finally {
508         try {
509           DBHelper.cleanup(rs);
510           DBHelper.cleanup(pstmt);
511         }
512         catch(PersistenceException pe) {
513           throw new SynchronisationException("JDBC error: ["+ pe.getMessage()+"]");
514         }
515       }
516 
517 
518     //4. update internal data members
519     if (name == null) {
520       //default as
521       this.defaultAnnots = as;
522     }
523     else {
524       //named as
525       this.namedAnnotSets.put(name,as);
526     }
527 
528     //don't return the new aset, the super method will take care
529     return;
530   }
531 
532 
533 
534 
535   private HashMap _readFeatures(Long asetID) {
536 
537     PreparedStatement pstmt = null;
538     ResultSet rs = null;
539 
540     //1
541     String      prevKey = DBHelper.DUMMY_FEATURE_KEY;
542     String      currKey = null;
543 
544     Integer     prevAnnID = null;
545     Integer     currAnnID = null;
546 
547     Object      currFeatureValue = null;
548     Vector      currFeatureArray = new Vector();
549 
550     HashMap     currFeatures = new HashMap();
551     FeatureMap  annFeatures = null;
552 
553     HashMap     featuresByAnnotID = new HashMap();
554 
555     //2. read the features from DB
556     try {
557       String sql = " select ann_local_id, " +
558                    "        ft_key, " +
559                    "        ft_value_type, " +
560                    "        ft_number_value, " +
561                    "        ft_character_value, " +
562                    "        ft_long_character_value, " +
563                    "        ft_binary_value " +
564                    " from  "+Gate.DB_OWNER+".v_annotation_features " +
565                    " where  set_id = ? " +
566                    " order by ann_local_id,ft_key ";
567 //System.out.println(sql);
568       pstmt = this.jdbcConn.prepareStatement(sql);
569       pstmt.setLong(1,asetID.longValue());
570       pstmt.execute();
571       rs = pstmt.getResultSet();
572 
573       while (rs.next()) {
574         //NOTE: because there are LOBs in the resulset
575         //the columns should be read in the order they appear
576         //in the query
577 
578         prevAnnID = currAnnID;
579         currAnnID = new Integer(rs.getInt(1));
580 
581         //2.1 is this a new Annotation?
582         if (!currAnnID.equals(prevAnnID) && prevAnnID != null) {
583           //new one
584           //2.1.1 normalize the hashmap with the features, and add
585           //the elements into a new FeatureMap
586           annFeatures = new SimpleFeatureMapImpl();
587           Set entries = currFeatures.entrySet();
588           Iterator itFeatureArrays = entries.iterator();
589 
590           while(itFeatureArrays.hasNext()) {
591             Map.Entry currEntry = (Map.Entry)itFeatureArrays.next();
592             String key = (String)currEntry.getKey();
593             Vector val = (Vector)currEntry.getValue();
594 
595             //add to feature map normalized array
596             Assert.assertTrue(val.size() >= 1);
597 
598             if (val.size() == 1) {
599               //the single elemnt of the array
600               annFeatures.put(key,val.firstElement());
601             }
602             else {
603               //the whole array
604               annFeatures.put(key,val);
605             }
606           }//while
607 
608           //2.1.2. add the featuremap for this annotation to the hashmap
609           featuresByAnnotID.put(prevAnnID,annFeatures);
610           //2.1.3. clear temp hashtable with feature vectors
611           currFeatures.clear();
612 /*??*/          prevAnnID = currAnnID;
613         }//if -- is new annotation
614 
615         currKey = rs.getString(2);
616         Long valueType = new Long(rs.getLong(3));
617 
618         //we don't quite know what is the type of the NUMBER
619         //stored in DB
620         Object numberValue = null;
621 
622         //for all numeric types + boolean -> read from DB as appropriate
623         //Java object
624         switch(valueType.intValue()) {
625 
626           case DBHelper.VALUE_TYPE_BOOLEAN:
627             numberValue = new Boolean(rs.getBoolean(4));
628             break;
629 
630           case DBHelper.VALUE_TYPE_FLOAT:
631             numberValue = new Float(rs.getFloat(4));
632             break;
633 
634           case DBHelper.VALUE_TYPE_INTEGER:
635             numberValue = new Integer(rs.getInt(4));
636             break;
637 
638           case DBHelper.VALUE_TYPE_LONG:
639             numberValue = new Long(rs.getLong(4));
640             break;
641 
642           default:
643             //do nothing, will be handled in the next switch statement
644         }
645 
646         //don't forget to read the rest of the current row
647         String stringValue = rs.getString(5);
648         Clob clobValue = rs.getClob(6);
649         Blob blobValue = rs.getBlob(7);
650 
651         switch(valueType.intValue()) {
652 
653           case DBHelper.VALUE_TYPE_BINARY:
654             throw new MethodNotImplementedException();
655 
656           case DBHelper.VALUE_TYPE_BOOLEAN:
657           case DBHelper.VALUE_TYPE_FLOAT:
658           case DBHelper.VALUE_TYPE_INTEGER:
659           case DBHelper.VALUE_TYPE_LONG:
660             currFeatureValue = numberValue;
661             break;
662 
663           case DBHelper.VALUE_TYPE_STRING:
664             //this one is tricky too
665             //if the string is < 4000 bytes long then it's stored as varchar2
666             //otherwise as CLOB
667             if (null == stringValue) {
668               //oops, we got CLOB
669               StringBuffer temp = new StringBuffer();
670               OracleDataStore.readCLOB(clobValue,temp);
671               currFeatureValue = temp.toString();
672             }
673             else {
674               currFeatureValue = stringValue;
675             }
676             break;
677 
678           default:
679             throw new SynchronisationException("Invalid feature type found in DB");
680         }//switch
681 
682         //ok, we got the key/value pair now
683         //2.2 is this a new feature key?
684         if (false == currFeatures.containsKey(currKey)) {
685           //new key
686           Vector keyValue = new Vector();
687           keyValue.add(currFeatureValue);
688           currFeatures.put(currKey,keyValue);
689         }
690         else {
691           //key is present, append to existing vector
692           ((Vector)currFeatures.get(currKey)).add(currFeatureValue);
693         }
694 
695         prevKey = currKey;
696       }//while
697 
698 
699       //2.3 process the last Annotation left
700       annFeatures = new SimpleFeatureMapImpl();
701 
702       Set entries = currFeatures.entrySet();
703       Iterator itFeatureArrays = entries.iterator();
704 
705       while(itFeatureArrays.hasNext()) {
706         Map.Entry currEntry = (Map.Entry)itFeatureArrays.next();
707         String key = (String)currEntry.getKey();
708         Vector val = (Vector)currEntry.getValue();
709 
710         //add to feature map normalized array
711         Assert.assertTrue(val.size() >= 1);
712 
713         if (val.size() == 1) {
714           //the single elemnt of the array
715           annFeatures.put(key,val.firstElement());
716         }
717         else {
718           //the whole array
719           annFeatures.put(key,val);
720         }
721       }//while
722 
723       //2.3.1. add the featuremap for this annotation to the hashmap
724       if (null != currAnnID) {
725         // do we have features at all for this annotation?
726         featuresByAnnotID.put(currAnnID,annFeatures);
727       }
728 
729       //3. return the hashmap
730       return featuresByAnnotID;
731     }
732     catch(SQLException sqle) {
733       throw new SynchronisationException("can't read content from DB: ["+ sqle.getMessage()+"]");
734     }
735     catch(IOException sqle) {
736       throw new SynchronisationException("can't read content from DB: ["+ sqle.getMessage()+"]");
737     }
738     finally {
739       try {
740         DBHelper.cleanup(rs);
741         DBHelper.cleanup(pstmt);
742       }
743       catch(PersistenceException pe) {
744         throw new SynchronisationException("JDBC error: ["+ pe.getMessage()+"]");
745       }
746     }
747   }
748 
749 
750   /** Set method for the document content */
751   public void setContent(DocumentContent content) {
752 
753     super.setContent(content);
754 
755     this.contentChanged = true;
756   }
757 
758   /** Set the feature set */
759   public void setFeatures(FeatureMap features) {
760     //1. save them first, so we can remove the listener
761     FeatureMap oldFeatures = this.features;
762 
763     super.setFeatures(features);
764 
765     this.featuresChanged = true;
766 
767     //4. sort out the listeners
768     if (eventHandler != null)
769       oldFeatures.removeFeatureMapListener(eventHandler);
770     else
771       eventHandler = new EventsHandler();
772     this.features.addFeatureMapListener(eventHandler);
773   }
774 
775   /** Sets the name of this resource*/
776   public void setName(String name){
777     super.setName(name);
778 
779     this.nameChanged = true;
780   }
781 
782 
783   private List getAnnotationsForOffset(AnnotationSet aDumpAnnotSet,Long offset){
784     throw new MethodNotImplementedException();
785   }
786 
787   /** Generate and return the next annotation ID */
788 /*  public Integer getNextAnnotationId() {
789 
790     //1.try to get ID fromt he pool
791     if (DEBUG) {
792       Out.println(">>> get annID called...");
793     }
794     //is there anything left in the pool?
795     if (this.SEQUENCE_POOL_SIZE == this.poolMarker) {
796       //oops, pool is empty
797       fillSequencePool();
798       this.poolMarker = 0;
799     }
800 
801     return this.sequencePool[this.poolMarker++];
802 
803     return super.getNextAnnotationId();
804   } // getNextAnnotationId
805 
806 
807   public void setNextAnnotationId(int aNextAnnotationId){
808 
809     //if u get this exception then u definitely don't have an idea what u're doing
810     throw new UnsupportedOperationException("Annotation IDs cannot be changed in " +
811                                             "database stores");
812   }// setNextAnnotationId();
813 
814 
815   private void fillSequencePool() {
816 
817     if(DEBUG) {
818       Out.println("filling ID lot...");
819     }
820 
821     CallableStatement stmt = null;
822     try {
823       stmt = this.jdbcConn.prepareCall(
824             "{ call "+Gate.DB_OWNER+".persist.get_id_lot(?,?,?,?,?,?,?,?,?,?) }");
825       stmt.registerOutParameter(1,java.sql.Types.BIGINT);
826       stmt.registerOutParameter(2,java.sql.Types.BIGINT);
827       stmt.registerOutParameter(3,java.sql.Types.BIGINT);
828       stmt.registerOutParameter(4,java.sql.Types.BIGINT);
829       stmt.registerOutParameter(5,java.sql.Types.BIGINT);
830       stmt.registerOutParameter(6,java.sql.Types.BIGINT);
831       stmt.registerOutParameter(7,java.sql.Types.BIGINT);
832       stmt.registerOutParameter(8,java.sql.Types.BIGINT);
833       stmt.registerOutParameter(9,java.sql.Types.BIGINT);
834       stmt.registerOutParameter(10,java.sql.Types.BIGINT);
835       stmt.execute();
836 
837       for (int i=0; i < this.SEQUENCE_POOL_SIZE; i++) {
838         //JDBC countsa from 1, not from 0
839         this.sequencePool[0] = new Integer(stmt.getInt(i+1));
840       }
841     }
842     catch(SQLException sqle) {
843       throw new SynchronisationException("can't get Annotation ID pool: ["+ sqle.getMessage()+"]");
844     }
845     finally {
846       try {
847         DBHelper.cleanup(stmt);
848       }
849       catch(PersistenceException pe) {
850         throw new SynchronisationException("JDBC error: ["+ pe.getMessage()+"]");
851       }
852     }
853   }
854 */
855 
856   public boolean isResourceChanged(int changeType) {
857 
858     switch(changeType) {
859 
860       case EventAwareLanguageResource.DOC_CONTENT:
861         return this.contentChanged;
862       case EventAwareLanguageResource.RES_FEATURES:
863         return this.featuresChanged;
864       case EventAwareLanguageResource.RES_NAME:
865         return this.nameChanged;
866       case EventAwareLanguageResource.DOC_MAIN:
867         return this.documentChanged;
868       default:
869         throw new IllegalArgumentException();
870     }
871 
872   }
873 
874   private void _setAnnotations(String setName,Collection annotations) {
875 
876     if (null == setName) {
877       Assert.assertTrue(null == this.defaultAnnots);
878       this.defaultAnnots = new DatabaseAnnotationSetImpl(this,annotations);
879 
880       //add to the set of loaded a-sets but do not add its annotations to the
881       //list of new annotations
882 //      this.loadedAnnotSets.add(this.defaultAnnots);
883     }
884     else {
885       Assert.assertTrue(false == this.namedAnnotSets.containsKey(setName));
886       AnnotationSet annSet = new DatabaseAnnotationSetImpl(this,setName,annotations);
887       this.namedAnnotSets.put(setName,annSet);
888 
889       //add to the set of loaded a-sets but do not add its annotations to the
890       //list of new annotations
891 //      this.loadedAnnotSets.add(annSet);
892     }
893   }
894 
895   /** Set method for the document's URL */
896   public void setSourceUrl(URL sourceUrl) {
897 
898     this.documentChanged = true;
899     super.setSourceUrl(sourceUrl);
900   } // setSourceUrl
901 
902 
903   /** Documents may be packed within files; in this case an optional pair of
904     * offsets refer to the location of the document. This method sets the
905     * end offset.
906     */
907   public void setSourceUrlEndOffset(Long sourceUrlEndOffset) {
908 
909     this.documentChanged = true;
910     super.setSourceUrlEndOffset(sourceUrlEndOffset);
911   } // setSourceUrlStartOffset
912 
913 
914   /** Documents may be packed within files; in this case an optional pair of
915     * offsets refer to the location of the document. This method sets the
916     * start offset.
917     */
918   public void setSourceUrlStartOffset(Long sourceUrlStartOffset) {
919 
920     this.documentChanged = true;
921     super.setSourceUrlStartOffset(sourceUrlStartOffset);
922   } // setSourceUrlStartOffset
923 
924   /** Make the document markup-aware. This will trigger the creation
925    *  of a DocumentFormat object at Document initialisation time; the
926    *  DocumentFormat object will unpack the markup in the Document and
927    *  add it as annotations. Documents are <B>not</B> markup-aware by default.
928    *
929    *  @param b markup awareness status.
930    */
931   public void setMarkupAware(Boolean newMarkupAware) {
932 
933     this.documentChanged = true;
934     super.setMarkupAware(newMarkupAware);
935   }
936 
937   /**
938    * All the events from the features are handled by
939    * this inner class.
940    */
941   class EventsHandler implements gate.event.FeatureMapListener {
942     public void featureMapUpdated(){
943       //tell the document that its features have been updated
944       featuresChanged = true;
945     }
946   }
947 
948   /**
949    * Overriden to remove the features listener, when the document is closed.
950    */
951   public void cleanup() {
952     super.cleanup();
953     if (eventHandler != null)
954       this.features.removeFeatureMapListener(eventHandler);
955   }///inner class EventsHandler
956 
957 
958   /**
959    * Called by a datastore when a new resource has been adopted
960    */
961   public void resourceAdopted(DatastoreEvent evt){
962   }
963 
964   /**
965    * Called by a datastore when a resource has been deleted
966    */
967   public void resourceDeleted(DatastoreEvent evt){
968 
969     Assert.assertNotNull(evt);
970     Assert.assertNotNull(evt.getResourceID());
971 
972     //unregister self as listener from the DataStore
973     if (evt.getResourceID().equals(this.getLRPersistenceId())) {
974       //someone deleted this document
975       getDataStore().removeDatastoreListener(this);
976     }
977 
978   }//resourceDeleted
979 
980   /**
981    * Called by a datastore when a resource has been wrote into the datastore
982    */
983   public void resourceWritten(DatastoreEvent evt){
984 
985     Assert.assertNotNull(evt);
986     Assert.assertNotNull(evt.getResourceID());
987 
988     //is the event for us?
989     if (evt.getResourceID().equals(this.getLRPersistenceId())) {
990       //wow, the event is for me
991       //clear all flags, the content is synced with the DB
992       this.contentChanged =
993         this.documentChanged =
994           this.featuresChanged =
995             this.nameChanged = false;
996 
997       this.removedAnotationSets.clear();
998       this.addedAnotationSets.clear();
999     }
1000
1001
1002  }
1003
1004  public Collection getLoadedAnnotationSets() {
1005
1006    //never return the data member - return a clone
1007    Assert.assertNotNull(this.namedAnnotSets);
1008    Vector result = new Vector(this.namedAnnotSets.values());
1009    if (null != this.defaultAnnots) {
1010      result.add(this.defaultAnnots);
1011    }
1012
1013    return result;
1014  }
1015
1016
1017  public Collection getRemovedAnnotationSets() {
1018
1019    //return a clone
1020    return new Vector(this.removedAnotationSets);
1021  }
1022
1023  public Collection getAddedAnnotationSets() {
1024
1025    //return a clone
1026    return new Vector(this.addedAnotationSets);
1027  }
1028
1029  public void removeAnnotationSet(String name) {
1030
1031    //1. add to the list of removed a-sets
1032    this.removedAnotationSets.add(name);
1033
1034    //if the set was read from the DB then it is registered as datastore listener and ...
1035    //there may be chnges in it
1036    //NOTE that default set cannot be reoved, so we just ignore it
1037
1038    if (this.namedAnnotSets.keySet().contains(name)) {
1039      //set was loaded
1040      AnnotationSet aset = (AnnotationSet)this.namedAnnotSets.get(name);
1041
1042      Assert.assertNotNull(aset);
1043      Assert.assertTrue(aset instanceof DatabaseAnnotationSetImpl);
1044
1045      //3. unregister it as a DataStoreListener
1046      this.dataStore.removeDatastoreListener((DatastoreListener)aset);
1047    }
1048
1049    //4. delegate
1050    super.removeAnnotationSet(name);
1051  }
1052
1053}