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