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