1   /*
2    *  PostgresDataStore.java
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Marin Dimitrov, 18/Mar/2001
12   *
13   *  $Id: PostgresDataStore.java,v 1.34 2003/01/07 17:15:02 valyt Exp $
14   */
15  
16  package gate.persist;
17  
18  import java.util.*;
19  import java.sql.*;
20  import java.net.*;
21  import java.io.*;
22  
23  import junit.framework.*;
24  
25  import gate.LanguageResource;
26  import gate.security.*;
27  import gate.security.SecurityException;
28  import gate.util.*;
29  import gate.corpora.*;
30  import gate.*;
31  
32  public class PostgresDataStore extends JDBCDataStore {
33  
34    /** Name of this resource */
35    private static final String DS_COMMENT = "GATE PostgreSQL datastore";
36  
37    /** the icon for this resource */
38    public static final String DS_ICON_NAME = "pgsql_ds.gif";
39  
40    /** Debug flag */
41    private static final boolean DEBUG = true;
42  
43    public PostgresDataStore() {
44  
45      super();
46      this.datastoreComment = DS_COMMENT;
47      this.iconName = DS_ICON_NAME;
48    }
49  
50  
51    public void setSecurityInfo(LanguageResource parm1, SecurityInfo parm2) throws gate.persist.PersistenceException, gate.security.SecurityException {
52      /**@todo: implement this gate.persist.JDBCDataStore abstract method*/
53      throw new MethodNotImplementedException();
54    }
55  
56    public List findLrIds(List constraints, String lrType) throws gate.persist.PersistenceException {
57      /**@todo: implement this gate.persist.JDBCDataStore abstract method*/
58      throw new MethodNotImplementedException();
59    }
60  
61  /*  public LanguageResource getLr(String lrClassName, Object lrPersistenceId) throws gate.security.SecurityException, gate.persist.PersistenceException {
62      throw new MethodNotImplementedException();
63    }
64  */
65  
66  /*  public void delete(String lrClassName, Object lrId) throws gate.security.SecurityException, gate.persist.PersistenceException {
67  
68      throw new MethodNotImplementedException();
69    }
70  */
71  
72    public List findLrIds(List constraints) throws gate.persist.PersistenceException {
73      /**@todo: implement this gate.persist.JDBCDataStore abstract method*/
74      throw new MethodNotImplementedException();
75    }
76  
77  
78    /**
79     * Releases the exlusive lock on a resource from the persistent store.
80     */
81    public void unlockLr(LanguageResource lr)
82    throws PersistenceException,SecurityException {
83  
84      //0. preconditions
85      Assert.assertNotNull(lr);
86      Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
87                        lr instanceof DatabaseCorpusImpl);
88      Assert.assertNotNull(lr.getLRPersistenceId());
89      Assert.assertEquals(lr.getDataStore(),this);
90  
91      //1. check session
92      if (null == this.session) {
93        throw new SecurityException("session not set");
94      }
95  
96      if (false == this.ac.isValidSession(this.session)) {
97        throw new SecurityException("invalid session supplied");
98      }
99  
100     //2. check permissions
101     if (false == canWriteLR(lr.getLRPersistenceId())) {
102       throw new SecurityException("no write access granted to the user");
103     }
104 
105     //3. try to unlock
106     PreparedStatement pstmt = null;
107     boolean lockSucceeded = false;
108 
109     try {
110       String sql = " select persist_unlock_lr(?,?) ";
111       pstmt = this.jdbcConn.prepareStatement(sql);
112       pstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
113       pstmt.setLong(2,this.session.getUser().getID().longValue());
114       pstmt.execute();
115       //we don't care about the result set
116     }
117     catch(SQLException sqle) {
118 
119       switch(sqle.getErrorCode()) {
120         case DBHelper.X_ORACLE_INVALID_LR:
121           throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
122         default:
123           throw new PersistenceException(
124                 "can't unlock LR in DB : ["+ sqle.getMessage()+"]");
125       }
126     }
127     finally {
128       DBHelper.cleanup(pstmt);
129     }
130   }
131 
132 
133   /**
134    * Checks if the user (identified by the sessionID)
135    * has some access (read/write) to the LR
136    */
137   protected boolean canAccessLR(Long lrID,int mode)
138     throws PersistenceException, SecurityException{
139 
140     //0. preconditions
141     Assert.assertTrue(DBHelper.READ_ACCESS == mode || DBHelper.WRITE_ACCESS == mode);
142 
143     //1. is session initialised?
144     if (null == this.session) {
145       throw new SecurityException("user session not set");
146     }
147 
148     //2.first check the session and then check whether the user is member of the group
149     if (this.ac.isValidSession(this.session) == false) {
150       throw new SecurityException("invalid session supplied");
151     }
152 
153     PreparedStatement pstmt = null;
154     ResultSet rs = null;
155 
156     try {
157       String sql = "select security_has_access_to_lr(?,?,?,?)";
158       pstmt = this.jdbcConn.prepareStatement(sql);
159       pstmt.setLong(1,lrID.longValue());
160       pstmt.setLong(2,this.session.getUser().getID().longValue());
161       pstmt.setLong(3,this.session.getGroup().getID().longValue());
162       pstmt.setLong(4,mode);
163       pstmt.execute();
164       rs = pstmt.getResultSet();
165 
166       if (false == rs.next()) {
167         throw new PersistenceException("empty result set");
168       }
169 
170       return rs.getBoolean(1);
171     }
172     catch(SQLException sqle) {
173       throw new PersistenceException("can't check permissions in DB: ["+ sqle.getMessage()+"]");
174     }
175     finally {
176       DBHelper.cleanup(rs);
177       DBHelper.cleanup(pstmt);
178     }
179 
180   }
181 
182 
183 
184   /**
185    * Try to acquire exlusive lock on a resource from the persistent store.
186    * Always call unlockLR() when the lock is no longer needed
187    */
188   public boolean lockLr(LanguageResource lr)
189   throws PersistenceException,SecurityException {
190 
191     //0. preconditions
192     Assert.assertNotNull(lr);
193     Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
194                       lr instanceof DatabaseCorpusImpl);
195     Assert.assertNotNull(lr.getLRPersistenceId());
196     Assert.assertEquals(lr.getDataStore(),this);
197 
198     //1. delegate
199     return _lockLr((Long)lr.getLRPersistenceId());
200   }
201 
202 
203   /**
204    *  helper for lockLR()
205    *  never call directly
206    */
207   private boolean _lockLr(Long lrID)
208   throws PersistenceException,SecurityException {
209 
210     //0. preconditions
211     Assert.assertNotNull(lrID);
212 
213     //1. check session
214     if (null == this.session) {
215       throw new SecurityException("session not set");
216     }
217 
218     if (false == this.ac.isValidSession(this.session)) {
219       throw new SecurityException("invalid session supplied");
220     }
221 
222     //2. check permissions
223     if (false == canWriteLR(lrID)) {
224       throw new SecurityException("no write access granted to the user");
225     }
226 
227     //3. try to lock
228     PreparedStatement pstmt = null;
229     ResultSet rset = null;
230     boolean lockSucceeded = false;
231 
232     try {
233       pstmt = this.jdbcConn.prepareStatement(" select persist_lock_lr(?,?,?) ");
234       pstmt.setLong(1,lrID.longValue());
235       pstmt.setLong(2,this.session.getUser().getID().longValue());
236       pstmt.setLong(3,this.session.getGroup().getID().longValue());
237 
238       pstmt.execute();
239       rset = pstmt.getResultSet();
240 
241       if (false == rset.next()) {
242         throw new PersistenceException("empty result set");
243       }
244 
245       lockSucceeded = rset.getBoolean(1);
246     }
247     catch(SQLException sqle) {
248 
249       switch(sqle.getErrorCode()) {
250         case DBHelper.X_ORACLE_INVALID_LR:
251           throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
252         default:
253           throw new PersistenceException(
254                 "can't lock LR in DB : ["+ sqle.getMessage()+"]");
255       }
256     }
257     finally {
258       DBHelper.cleanup(rset);
259       DBHelper.cleanup(pstmt);
260     }
261 
262     return lockSucceeded;
263   }
264 
265 
266 /*  protected Corpus createCorpus(Corpus corp,SecurityInfo secInfo, boolean newTransPerDocument)
267     throws PersistenceException,SecurityException {
268 
269     throw new MethodNotImplementedException();
270   }
271 */
272   /**
273    *  helper for adopt()
274    *  never call directly
275    */
276   protected Long createLR(String lrType,
277                           String lrName,
278                           SecurityInfo si,
279                           Long lrParentID)
280     throws PersistenceException,SecurityException {
281 
282     //0. preconditions
283     Assert.assertNotNull(lrName);
284 
285     //1. check the session
286 //    if (this.ac.isValidSession(s) == false) {
287 //      throw new SecurityException("invalid session provided");
288 //    }
289 
290     //2. create a record in DB
291     PreparedStatement pstmt = null;
292     ResultSet rset = null;
293 
294     try {
295       String sql = " select persist_create_lr(?,?,?,?,?,?) ";
296       pstmt = this.jdbcConn.prepareStatement(sql);
297       pstmt.setLong(1,si.getUser().getID().longValue());
298       pstmt.setLong(2,si.getGroup().getID().longValue());
299       pstmt.setString(3,lrType);
300       pstmt.setString(4,lrName);
301       pstmt.setInt(5,si.getAccessMode());
302       if (null == lrParentID) {
303         pstmt.setNull(6,java.sql.Types.INTEGER);
304       }
305       else {
306         pstmt.setLong(6,lrParentID.longValue());
307       }
308 
309       pstmt.execute();
310       rset = pstmt.getResultSet();
311       if (false == rset.next()) {
312         throw new PersistenceException("empty result set");
313       }
314 
315       Long result =  new Long(rset.getLong(1));
316 
317       return result;
318     }
319     catch(SQLException sqle) {
320 
321       switch(sqle.getErrorCode()) {
322         case DBHelper.X_ORACLE_INVALID_LR_TYPE:
323           throw new PersistenceException("can't create LR [step 3] in DB, invalid LR Type");
324         default:
325           throw new PersistenceException(
326                 "can't create LR [step 3] in DB : ["+ sqle.getMessage()+"]");
327       }
328     }
329     finally {
330       DBHelper.cleanup(rset);
331       DBHelper.cleanup(pstmt);
332     }
333   }
334 
335 
336   /**
337    * helper for adopt
338    * never call directly
339    */
340   protected Long createDoc(Long _lrID,
341                           URL _docURL,
342                           String _docEncoding,
343                           Long _docStartOffset,
344                           Long _docEndOffset,
345                           Boolean _docIsMarkupAware,
346                           Long _corpusID)
347     throws PersistenceException {
348 
349     PreparedStatement pstmt = null;
350     ResultSet rset = null;
351     Long docID = null;
352 
353     try {
354       pstmt = this.jdbcConn.prepareStatement(
355                 " select persist_create_document(?,?,?,?,?,?,?) ");
356       pstmt.setLong(1,_lrID.longValue());
357       pstmt.setString(2,_docURL != null ? _docURL.toString() : "" );
358       //do we have doc encoding?
359       if (null == _docEncoding) {
360         pstmt.setNull(3,java.sql.Types.VARCHAR);
361       }
362       else {
363         pstmt.setString(3,_docEncoding);
364       }
365       //do we have start offset?
366       if (null==_docStartOffset) {
367         pstmt.setNull(4,java.sql.Types.INTEGER);
368       }
369       else {
370         pstmt.setLong(4,_docStartOffset.longValue());
371       }
372       //do we have end offset?
373       if (null==_docEndOffset) {
374         pstmt.setNull(5,java.sql.Types.INTEGER);
375       }
376       else {
377         pstmt.setLong(5,_docEndOffset.longValue());
378       }
379 
380       pstmt.setBoolean(6,_docIsMarkupAware.booleanValue());
381 
382       //is the document part of a corpus?
383       if (null == _corpusID) {
384         pstmt.setNull(7,java.sql.Types.BIGINT);
385       }
386       else {
387         pstmt.setLong(7,_corpusID.longValue());
388       }
389 
390       pstmt.execute();
391       rset = pstmt.getResultSet();
392       if (false == rset.next()) {
393         throw new PersistenceException("empty result set");
394       }
395 
396       docID = new Long(rset.getLong(1));
397 
398       return docID;
399 
400     }
401     catch(SQLException sqle) {
402       throw new PersistenceException("can't create document [step 4] in DB: ["+ sqle.getMessage()+"]");
403     }
404     finally {
405       DBHelper.cleanup(rset);
406       DBHelper.cleanup(pstmt);
407     }
408 
409   }
410 
411 
412   /** creates an entry for annotation set in the database */
413   protected void createAnnotationSet(Long lrID, AnnotationSet aset)
414     throws PersistenceException {
415 
416     //1. create a-set
417     String asetName = aset.getName();
418     Long asetID = null;
419 
420     //DB stuff
421     PreparedStatement pstmt = null;
422     ResultSet rs = null;
423 
424     try {
425       String sql = "select persist_create_annotation_set(?,?)";
426       pstmt = this.jdbcConn.prepareStatement(sql);
427 
428       pstmt.setLong(1,lrID.longValue());
429       if (null == asetName) {
430         pstmt.setNull(2,java.sql.Types.VARCHAR);
431       }
432       else {
433         pstmt.setString(2,asetName);
434       }
435       pstmt.execute();
436       rs = pstmt.getResultSet();
437 
438       if (false == rs.next()) {
439         throw new PersistenceException("empty result set");
440       }
441 
442       asetID = new Long(rs.getLong(1));
443     }
444     catch(SQLException sqle) {
445       throw new PersistenceException("can't create a-set [step 1] in DB: ["+ sqle.getMessage()+"]");
446     }
447     finally {
448       DBHelper.cleanup(rs);
449       DBHelper.cleanup(pstmt);
450     }
451 
452 
453     //2. insert annotations/nodes for DEFAULT a-set
454     //for now use a stupid cycle
455     //TODO: pass all the data with one DB call (?)
456 
457     try {
458       String sql = "select persist_create_annotation(?,?,?,?,?,?,?,?) ";
459       pstmt = this.jdbcConn.prepareStatement(sql);
460 
461 
462       Iterator itAnnotations = aset.iterator();
463 
464       while (itAnnotations.hasNext()) {
465         Annotation ann = (Annotation)itAnnotations.next();
466         Node start = (Node)ann.getStartNode();
467         Node end = (Node)ann.getEndNode();
468         String type = ann.getType();
469 
470         //DB stuff
471         Long annGlobalID = null;
472         pstmt.setLong(1,lrID.longValue());
473         pstmt.setLong(2,ann.getId().longValue());
474         pstmt.setLong(3,asetID.longValue());
475         pstmt.setLong(4,start.getId().longValue());
476         pstmt.setLong(5,start.getOffset().longValue());
477         pstmt.setLong(6,end.getId().longValue());
478         pstmt.setLong(7,end.getOffset().longValue());
479         pstmt.setString(8,type);
480         pstmt.execute();
481         rs = pstmt.getResultSet();
482 
483         if (false == rs.next()) {
484           throw new PersistenceException("empty result set");
485         }
486 
487         annGlobalID = new Long(rs.getLong(1));
488         DBHelper.cleanup(rs);
489 
490         //2.1. set annotation features
491         FeatureMap features = ann.getFeatures();
492         Assert.assertNotNull(features);
493         createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
494 //        createFeaturesBulk(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
495       } //while
496     }//try
497     catch(SQLException sqle) {
498 
499       switch(sqle.getErrorCode()) {
500 
501         case DBHelper.X_ORACLE_INVALID_ANNOTATION_TYPE:
502           throw new PersistenceException(
503                               "can't create annotation in DB, [invalid annotation type]");
504         default:
505           throw new PersistenceException(
506                 "can't create annotation in DB: ["+ sqle.getMessage()+"]");
507       }//switch
508     }//catch
509     finally {
510       DBHelper.cleanup(pstmt);
511     }
512   }
513 
514   /**
515    *  updates the content of the document if it is binary or a long string
516    *  (that does not fit into VARCHAR2)
517    */
518   protected void updateDocumentContent(Long docID,DocumentContent content)
519     throws PersistenceException {
520 
521     //1. get LOB locators from DB
522     PreparedStatement pstmt = null;
523     try {
524       String sql =  " update  t_doc_content "      +
525                     " set     dc_character_content = ?,  " +
526                     "         dc_content_type = ? " +
527                     " where   dc_id = (select doc_content_id " +
528                     "                   from t_document " +
529                     "                   where doc_id = ?) ";
530 
531       pstmt = this.jdbcConn.prepareStatement(sql);
532       pstmt.setString(1,content.toString());
533       pstmt.setInt(2,DBHelper.CHARACTER_CONTENT);
534       pstmt.setLong(3,docID.longValue());
535       pstmt.executeUpdate();
536     }
537     catch(SQLException sqle) {
538       throw new PersistenceException("can't update document content in DB : ["+
539                                       sqle.getMessage()+"]");
540     }
541     finally {
542       DBHelper.cleanup(pstmt);
543     }
544 
545   }
546 
547 
548 
549 
550   /**
551    *  creates a feature with the specified type/key/value for the specified entity
552    *  entitties are either LRs ot Annotations
553    *  valid values are: boolean,
554    *                    int,
555    *                    long,
556    *                    string,
557    *                    float,
558    *                    Object,
559    *                    boolean List,
560    *                    int List,
561    *                    long List,
562    *                    string List,
563    *                    float List,
564    *                    Object List
565    *
566    */
567 
568   private void createFeature(Long entityID, int entityType,String key, Object value, PreparedStatement pstmt)
569     throws PersistenceException {
570 
571     //1. what kind of feature value is this?
572     int valueType = findFeatureType(value);
573 
574     //2. how many elements do we store?
575     Vector elementsToStore = new Vector();
576 
577     switch(valueType) {
578       case DBHelper.VALUE_TYPE_NULL:
579       case DBHelper.VALUE_TYPE_BINARY:
580       case DBHelper.VALUE_TYPE_BOOLEAN:
581       case DBHelper.VALUE_TYPE_FLOAT:
582       case DBHelper.VALUE_TYPE_INTEGER:
583       case DBHelper.VALUE_TYPE_LONG:
584       case DBHelper.VALUE_TYPE_STRING:
585         elementsToStore.add(value);
586         break;
587 
588       default:
589         //arrays
590         List arr = (List)value;
591         Iterator itValues = arr.iterator();
592 
593         while (itValues.hasNext()) {
594           elementsToStore.add(itValues.next());
595         }
596 
597         //normalize , i.e. ignore arrays
598         if (valueType == DBHelper.VALUE_TYPE_BINARY_ARR)
599           valueType = DBHelper.VALUE_TYPE_BINARY;
600         else if (valueType == DBHelper.VALUE_TYPE_BOOLEAN_ARR)
601           valueType = DBHelper.VALUE_TYPE_BOOLEAN;
602         else if (valueType == DBHelper.VALUE_TYPE_FLOAT_ARR)
603           valueType = DBHelper.VALUE_TYPE_FLOAT;
604         else if (valueType == DBHelper.VALUE_TYPE_INTEGER_ARR)
605           valueType = DBHelper.VALUE_TYPE_INTEGER;
606         else if (valueType == DBHelper.VALUE_TYPE_LONG_ARR)
607           valueType = DBHelper.VALUE_TYPE_LONG;
608         else if (valueType == DBHelper.VALUE_TYPE_STRING_ARR)
609           valueType = DBHelper.VALUE_TYPE_STRING;
610     }
611 
612     //3. for all elements:
613     for (int i=0; i< elementsToStore.size(); i++) {
614 
615         Object currValue = elementsToStore.elementAt(i);
616 
617         //3.1. create a dummy feature [LOB hack]
618         Long featID = _createFeature(entityID,entityType,key,currValue,valueType,pstmt);
619     }
620 
621   }
622 
623 
624 
625   /**
626    *  helper metod
627    *  iterates a FeatureMap and creates all its features in the database
628    */
629   protected void createFeatures(Long entityID, int entityType, FeatureMap features)
630     throws PersistenceException {
631 
632     //0. prepare statement ad use it for all features
633     PreparedStatement pstmt = null;
634 
635     try {
636       String sql = "select persist_create_feature(?,?,?,?,?,?,?,?) ";
637       pstmt = this.jdbcConn.prepareStatement(sql);
638     }
639     catch (SQLException sqle) {
640       throw new PersistenceException(sqle);
641     }
642 
643     /* when some day Java has macros, this will be a macro */
644     Set entries = features.entrySet();
645     Iterator itFeatures = entries.iterator();
646     while (itFeatures.hasNext()) {
647       Map.Entry entry = (Map.Entry)itFeatures.next();
648       String key = (String)entry.getKey();
649       Object value = entry.getValue();
650       createFeature(entityID,entityType,key,value,pstmt);
651     }
652 
653     //3. cleanup
654     DBHelper.cleanup(pstmt);
655   }
656 
657   protected void createFeaturesBulk(Long entityID, int entityType, FeatureMap features)
658     throws PersistenceException {
659 
660     throw new MethodNotImplementedException();
661   }
662 
663   /**
664    *  creates a feature of the specified type/value/valueType/key for the specified entity
665    *  Entity is one of: LR, Annotation
666    *  Value types are: boolean, int, long, string, float, Object
667    */
668   private Long _createFeature(Long entityID,
669                               int entityType,
670                               String key,
671                               Object value,
672                               int valueType,
673                               PreparedStatement pstmt)
674     throws PersistenceException {
675 
676     //1. store in DB
677     Long featID = null;
678     ResultSet rs = null;
679 
680     try {
681 
682       //1.1 set known values + NULLs
683       pstmt.setLong(1,entityID.longValue());
684       pstmt.setInt(2,entityType);
685       pstmt.setString(3,key);
686       pstmt.setNull(4,java.sql.Types.BIGINT);
687       pstmt.setNull(5,java.sql.Types.DOUBLE);
688       pstmt.setNull(6,java.sql.Types.LONGVARCHAR);
689       pstmt.setNull(7,java.sql.Types.LONGVARBINARY);
690       pstmt.setInt(8,valueType);
691 
692       //1.2 set proper data
693       switch(valueType) {
694 
695         case DBHelper.VALUE_TYPE_NULL:
696           break;
697 
698         case DBHelper.VALUE_TYPE_BOOLEAN:
699 
700           boolean b = ((Boolean)value).booleanValue();
701           pstmt.setLong(4, b ? DBHelper.TRUE : DBHelper.FALSE);
702           break;
703 
704         case DBHelper.VALUE_TYPE_INTEGER:
705 
706           pstmt.setLong(4,((Integer)value).intValue());
707           break;
708 
709         case DBHelper.VALUE_TYPE_LONG:
710 
711           pstmt.setLong(4,((Long)value).longValue());
712           break;
713 
714         case DBHelper.VALUE_TYPE_FLOAT:
715 
716           Double d = (Double)value;
717           pstmt.setDouble(5,d.doubleValue());
718           break;
719 
720         case DBHelper.VALUE_TYPE_BINARY:
721           //we serialize the value (object) in the DB
722           ByteArrayOutputStream baos = new ByteArrayOutputStream();
723           ObjectOutputStream oos = new ObjectOutputStream(baos);
724           oos.writeObject(value);
725           oos.close();
726           baos.close();
727           byte[] buff = baos.toByteArray();
728           ByteArrayInputStream bais = new ByteArrayInputStream(buff);
729           pstmt.setBinaryStream(7,bais,buff.length);
730           bais.close();
731           break;
732 
733         case DBHelper.VALUE_TYPE_STRING:
734 
735           String s = (String)value;
736           //does it fin into a varchar2?
737           pstmt.setString(6,s);
738           break;
739 
740         default:
741           throw new IllegalArgumentException("unsuppoeted feature type");
742       }
743 
744       pstmt.execute();
745       rs = pstmt.getResultSet();
746 
747       if (false == rs.next()) {
748         throw new PersistenceException("empty result set");
749       }
750 
751       featID = new Long(rs.getLong(1));
752     }
753     catch(IOException ioe) {
754       throw new PersistenceException("can't write binary data ["+ioe.getMessage()+"]");
755     }
756     catch(SQLException sqle) {
757 
758       switch(sqle.getErrorCode()) {
759         case DBHelper.X_ORACLE_INVALID_FEATURE_TYPE:
760           throw new PersistenceException("can't create feature [step 1],"+
761                       "[invalid feature type] in DB: ["+ sqle.getMessage()+"]");
762         default:
763           throw new PersistenceException("can't create feature [step 1] in DB: ["+
764                                                       sqle.getMessage()+"]");
765       }
766     }
767     finally {
768       DBHelper.cleanup(rs);
769 //      DBHelper.cleanup(stmt);
770     }
771 
772     return featID;
773   }
774 
775 
776   /**
777    *  updates the value of a feature where the value is string (>4000 bytes, stored as CLOB)
778    *  or Object (stored as BLOB)
779    */
780 /*  private void _updateFeatureLOB(Long featID,Object value, int valueType)
781     throws PersistenceException {
782 
783     throw new MethodNotImplementedException();
784   }
785 */
786   /** helper for sync() - saves a Corpus in the database */
787 /*  protected void syncCorpus(Corpus corp)
788     throws PersistenceException,SecurityException {
789 
790     throw new MethodNotImplementedException();
791   }
792 */
793 
794   /**
795    *  helper for sync()
796    *  NEVER call directly
797    */
798   protected void _syncLR(LanguageResource lr)
799     throws PersistenceException,SecurityException {
800 
801     //0.preconditions
802     Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
803                       lr instanceof DatabaseCorpusImpl);;
804     Assert.assertNotNull(lr.getLRPersistenceId());
805 
806     PreparedStatement pstmt = null;
807 
808     try {
809       pstmt = this.jdbcConn.prepareStatement("select persist_update_lr(?,?,?)");
810       pstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
811       pstmt.setString(2,lr.getName());
812       //do we have a parent resource?
813       if (lr instanceof Document &&
814           null != lr.getParent()) {
815         pstmt.setLong(3,((Long)lr.getParent().getLRPersistenceId()).longValue());
816       }
817       else {
818         pstmt.setNull(3,java.sql.Types.BIGINT);
819       }
820 
821       pstmt.execute();
822     }
823     catch(SQLException sqle) {
824 
825       switch(sqle.getErrorCode()) {
826         case DBHelper.X_ORACLE_INVALID_LR:
827           throw new PersistenceException("can't set LR name in DB: [invalid LR ID]");
828         default:
829           throw new PersistenceException(
830                 "can't set LR name in DB: ["+ sqle.getMessage()+"]");
831       }
832 
833     }
834     finally {
835       DBHelper.cleanup(pstmt);
836     }
837 
838   }
839 
840   /** helper for sync() - never call directly */
841   protected void _syncDocumentHeader(Document doc)
842     throws PersistenceException {
843 
844     Long lrID = (Long)doc.getLRPersistenceId();
845 
846     PreparedStatement pstmt = null;
847 
848     try {
849       pstmt = this.jdbcConn.prepareStatement("select persist_update_document(?,?,?,?,?)");
850       pstmt.setLong(1,lrID.longValue());
851       if(doc.getSourceUrl() != null){
852         pstmt.setString(2, doc.getSourceUrl().toString());
853       }else{
854         pstmt.setString(2, "");
855       }
856       //do we have start offset?
857       if (null==doc.getSourceUrlStartOffset()) {
858         pstmt.setNull(3,java.sql.Types.INTEGER);
859       }
860       else {
861         pstmt.setLong(3,doc.getSourceUrlStartOffset().longValue());
862       }
863       //do we have end offset?
864       if (null==doc.getSourceUrlEndOffset()) {
865         pstmt.setNull(4,java.sql.Types.INTEGER);
866       }
867       else {
868         pstmt.setLong(4,doc.getSourceUrlEndOffset().longValue());
869       }
870 
871       pstmt.setBoolean(5,doc.getMarkupAware().booleanValue());
872       pstmt.execute();
873     }
874     catch(SQLException sqle) {
875 
876       switch(sqle.getErrorCode()) {
877         case DBHelper.X_ORACLE_INVALID_LR :
878           throw new PersistenceException("invalid LR supplied: no such document: ["+
879                                                             sqle.getMessage()+"]");
880         default:
881           throw new PersistenceException("can't change document data: ["+
882                                                             sqle.getMessage()+"]");
883       }
884     }
885     finally {
886       DBHelper.cleanup(pstmt);
887     }
888 
889   }
890 
891   /** helper for sync() - never call directly */
892   protected void _syncDocumentContent(Document doc)
893     throws PersistenceException {
894 
895     //0.
896     Assert.assertNotNull(doc);
897     Assert.assertNotNull(doc.getLRPersistenceId());
898     Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
899 
900     PreparedStatement pstmt = null;
901     //1.
902     try {
903       pstmt = this.jdbcConn.prepareStatement("select persist_update_document_content(?,?)");
904       pstmt.setLong(1,((Long)doc.getLRPersistenceId()).longValue());
905 
906       DocumentContent dc = doc.getContent();
907       if (dc.size().longValue() > 0) {
908         pstmt.setString(2,dc.toString());
909       }
910       else {
911         pstmt.setNull(2,java.sql.Types.LONGVARCHAR);
912       }
913 
914       pstmt.execute();
915     }
916     catch(SQLException sqle) {
917       throw new PersistenceException("Cannot update document content ["+
918                                       sqle.getMessage()+"]");
919     }
920     finally {
921       DBHelper.cleanup(pstmt);
922     }
923   }
924 
925   /** helper for sync() - never call directly */
926   protected void _syncFeatures(LanguageResource lr)
927     throws PersistenceException {
928 
929     //0. preconditions
930     Assert.assertNotNull(lr);
931     Assert.assertNotNull(lr.getLRPersistenceId());
932     Assert.assertEquals(((DatabaseDataStore)lr.getDataStore()).getDatabaseID(),
933                       this.getDatabaseID());
934     Assert.assertTrue(lr instanceof Document || lr instanceof Corpus);
935     //we have to be in the context of transaction
936 
937     //1, get ID  in the DB
938     Long lrID = (Long)lr.getLRPersistenceId();
939     int  entityType;
940 
941     //2. delete features
942     PreparedStatement pstmt = null;
943     try {
944       Assert.assertTrue(false == this.jdbcConn.getAutoCommit());
945       pstmt = this.jdbcConn.prepareStatement("select persist_delete_features(?,?) ");
946       pstmt.setLong(1,lrID.longValue());
947 
948       if (lr instanceof Document) {
949         entityType = DBHelper.FEATURE_OWNER_DOCUMENT;
950       }
951       else if (lr instanceof Corpus) {
952         entityType = DBHelper.FEATURE_OWNER_CORPUS;
953       }
954       else {
955         throw new IllegalArgumentException();
956       }
957 
958       pstmt.setInt(2,entityType);
959       pstmt.execute();
960     }
961     catch(SQLException sqle) {
962       throw new PersistenceException("can't delete features in DB: ["+ sqle.getMessage()+"]");
963     }
964     finally {
965       DBHelper.cleanup(pstmt);
966     }
967 
968     //3. recreate them
969     createFeatures(lrID,entityType, lr.getFeatures());
970   }
971 
972   /** helper for sync() - never call directly */
973 /*  protected void _syncAnnotationSets(Document doc,Collection removedSets,Collection addedSets)
974     throws PersistenceException {
975 
976     throw new MethodNotImplementedException();
977   }
978 */
979 
980   /** helper for sync() - never call directly */
981 /*  protected void _syncAddedAnnotations(Document doc, AnnotationSet as, Collection changes)
982     throws PersistenceException {
983 
984     throw new MethodNotImplementedException();
985   }
986 */
987 
988   /** helper for sync() - never call directly */
989 /*  protected void _syncRemovedAnnotations(Document doc,AnnotationSet as, Collection changes)
990     throws PersistenceException {
991 
992     throw new MethodNotImplementedException();
993   }
994 */
995   /** helper for sync() - never call directly */
996 /*  protected void _syncChangedAnnotations(Document doc,AnnotationSet as, Collection changes)
997     throws PersistenceException {
998 
999     throw new MethodNotImplementedException();
1000  }
1001*/
1002
1003  /**
1004   *  reads the features of an entity
1005   *  entities are of type LR or Annotation
1006   */
1007  protected FeatureMap readFeatures(Long entityID, int entityType)
1008    throws PersistenceException {
1009
1010    //0. preconditions
1011    Assert.assertNotNull(entityID);
1012    Assert.assertTrue(entityType == DBHelper.FEATURE_OWNER_ANNOTATION ||
1013                  entityType == DBHelper.FEATURE_OWNER_CORPUS ||
1014                  entityType == DBHelper.FEATURE_OWNER_DOCUMENT);
1015
1016
1017    PreparedStatement pstmt = null;
1018    ResultSet rs = null;
1019    FeatureMap fm = new SimpleFeatureMapImpl();
1020
1021    //1. read from DB
1022    try {
1023      String sql = " select ftkey.fk_string, " +
1024                   "        ft.ft_value_type, " +
1025                   "        ft.ft_int_value, " +
1026                   "        ft.ft_float_value, " +
1027                   "        ft.ft_binary_value, " +
1028                   "        ft.ft_character_value " +
1029                   " from   t_feature ft, " +
1030                   "        t_feature_key ftkey " +
1031                   " where  ft.ft_entity_id = ? " +
1032                   "        and ft.ft_entity_type = ? " +
1033                   "        and ft.ft_key_id = ftkey.fk_id " +
1034                   " order by ftkey.fk_string,ft.ft_id";
1035
1036      pstmt = this.jdbcConn.prepareStatement(sql);
1037      pstmt.setLong(1,entityID.longValue());
1038      pstmt.setLong(2,entityType);
1039      pstmt.execute();
1040      rs = pstmt.getResultSet();
1041
1042      //3. fill feature map
1043      Vector arrFeatures = new Vector();
1044      String prevKey = null;
1045      String currKey = null;
1046      Object currFeature = null;
1047
1048
1049      while (rs.next()) {
1050        //NOTE: because there are LOBs in the resulset
1051        //the columns should be read in the order they appear
1052        //in the query
1053        currKey = rs.getString("fk_string");
1054
1055        Long valueType = new Long(rs.getLong("ft_value_type"));
1056
1057        //we don't quite know what is the type of the NUMBER
1058        //stored in DB
1059        Object numberValue = null;
1060
1061        //for all numeric types + boolean -> read from DB as appropriate
1062        //Java object
1063        switch(valueType.intValue()) {
1064
1065          case DBHelper.VALUE_TYPE_BOOLEAN:
1066            numberValue = new Boolean(rs.getBoolean("ft_int_value"));
1067            break;
1068
1069          case DBHelper.VALUE_TYPE_FLOAT:
1070            numberValue = new Double(rs.getDouble("ft_float_value"));
1071            break;
1072
1073          case DBHelper.VALUE_TYPE_INTEGER:
1074            numberValue = new Integer(rs.getInt("ft_int_value"));
1075            break;
1076
1077          case DBHelper.VALUE_TYPE_LONG:
1078            numberValue = new Long(rs.getLong("ft_int_value"));
1079            break;
1080        }
1081
1082        //don't forget to read the rest of the current row
1083        InputStream blobValue = rs.getBinaryStream("ft_binary_value");
1084        String stringValue = rs.getString("ft_character_value");
1085
1086        switch(valueType.intValue()) {
1087
1088          case DBHelper.VALUE_TYPE_NULL:
1089            currFeature = null;
1090            break;
1091
1092          case DBHelper.VALUE_TYPE_BOOLEAN:
1093          case DBHelper.VALUE_TYPE_FLOAT:
1094          case DBHelper.VALUE_TYPE_INTEGER:
1095          case DBHelper.VALUE_TYPE_LONG:
1096            currFeature = numberValue;
1097            break;
1098
1099          case DBHelper.VALUE_TYPE_BINARY:
1100            //deserialize a java object
1101            ObjectInputStream ois = new ObjectInputStream(blobValue);
1102            currFeature = ois.readObject();
1103            ois.close();
1104            blobValue.close();
1105            break;
1106
1107          case DBHelper.VALUE_TYPE_STRING:
1108            currFeature = stringValue;
1109            break;
1110
1111          default:
1112            throw new PersistenceException("Invalid feature type found in DB, type is ["+valueType.intValue()+"]");
1113        }//switch
1114
1115        //new feature or part of an array?
1116        if (currKey.equals(prevKey) && prevKey != null) {
1117          //part of array
1118          arrFeatures.add(currFeature);
1119        }
1120        else {
1121          //add prev feature to feature map
1122
1123          //is the prev feature an array or a single object?
1124          if (arrFeatures.size() > 1) {
1125            //put a clone, because this is a temp array that will
1126            //be cleared in few lines
1127            fm.put(prevKey, new Vector(arrFeatures));
1128          }
1129          else if (arrFeatures.size() == 1) {
1130            fm.put(prevKey,arrFeatures.elementAt(0));
1131          }
1132          else {
1133            //do nothing, this is the dummy feature
1134            ;
1135          }//if
1136
1137          //now clear the array from previous fesature(s) and put the new
1138          //one there
1139          arrFeatures.clear();
1140
1141          prevKey = currKey;
1142          arrFeatures.add(currFeature);
1143        }//if
1144      }//while
1145
1146      //add the last feature
1147      if (arrFeatures.size() > 1) {
1148        fm.put(currKey,arrFeatures);
1149      }
1150      else if (arrFeatures.size() == 1) {
1151        fm.put(currKey,arrFeatures.elementAt(0));
1152      }
1153    }//try
1154    catch(SQLException sqle) {
1155      throw new PersistenceException("can't read features from DB: ["+ sqle.getMessage()+"]");
1156    }
1157    catch(IOException ioe) {
1158      throw new PersistenceException("can't read features from DB: ["+ ioe.getMessage()+"]");
1159    }
1160    catch(ClassNotFoundException cnfe) {
1161      throw new PersistenceException("can't read features from DB: ["+ cnfe.getMessage()+"]");
1162    }
1163    finally {
1164      DBHelper.cleanup(rs);
1165      DBHelper.cleanup(pstmt);
1166    }
1167
1168    return fm;
1169  }
1170
1171
1172  /**
1173   *  helper method for delete()
1174   *  never call it directly beause proper events will not be fired
1175   */
1176  protected void deleteDocument(Long lrId)
1177  throws PersistenceException {
1178    //0. preconditions
1179    Assert.assertNotNull(lrId);
1180
1181    PreparedStatement pstmt = null;
1182
1183    //1. delete from DB
1184    try {
1185      pstmt = this.jdbcConn.prepareStatement("select persist_delete_document(?) ");
1186      pstmt.setLong(1,lrId.longValue());
1187      pstmt.execute();
1188    }
1189    catch(SQLException sqle) {
1190      throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
1191    }
1192    finally {
1193      DBHelper.cleanup(pstmt);
1194    }
1195  }
1196
1197  /**
1198   *  helper method for delete()
1199   *  never call it directly beause proper events will not be fired
1200   */
1201  protected void deleteCorpus(Long lrId)
1202    throws PersistenceException {
1203
1204    Long ID = (Long)lrId;
1205
1206    PreparedStatement pstmt = null;
1207
1208    try {
1209      pstmt = this.jdbcConn.prepareStatement("select persist_delete_corpus(?)");
1210      pstmt.setLong(1,ID.longValue());
1211      pstmt.execute();
1212    }
1213    catch(SQLException sqle) {
1214      throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
1215    }
1216    finally {
1217      DBHelper.cleanup(pstmt);
1218    }
1219  }
1220
1221
1222  /** helper for sync() - never call directly */
1223  protected void _syncRemovedDocumentsFromCorpus(List docLRIDs, Long corpLRID)
1224    throws PersistenceException {
1225
1226    //0.preconditions
1227    Assert.assertNotNull(docLRIDs);
1228    Assert.assertNotNull(corpLRID);
1229    Assert.assertTrue(docLRIDs.size() > 0);
1230
1231    PreparedStatement pstmt = null;
1232
1233    try {
1234      pstmt = this.jdbcConn.prepareStatement("select persist_remove_doc_from_corpus(?,?)");
1235
1236      Iterator it = docLRIDs.iterator();
1237      while (it.hasNext()) {
1238        Long currLRID = (Long)it.next();
1239        pstmt.setLong(1,currLRID.longValue());
1240        pstmt.setLong(2,corpLRID.longValue());
1241        pstmt.execute();
1242      }
1243    }
1244    catch(SQLException sqle) {
1245
1246      switch(sqle.getErrorCode()) {
1247        case DBHelper.X_ORACLE_INVALID_LR :
1248          throw new PersistenceException("invalid LR supplied: no such document: ["+
1249                                                            sqle.getMessage()+"]");
1250        default:
1251          throw new PersistenceException("can't change document data: ["+
1252                                                            sqle.getMessage()+"]");
1253      }
1254    }
1255    finally {
1256      DBHelper.cleanup(pstmt);
1257    }
1258
1259  }
1260
1261  /**
1262   *   adds document to corpus in the database
1263   *   if the document is already part of the corpus nothing
1264   *   changes
1265   */
1266  protected void addDocumentToCorpus(Long docID,Long corpID)
1267  throws PersistenceException,SecurityException {
1268
1269    //0. preconditions
1270    Assert.assertNotNull(docID);
1271    Assert.assertNotNull(corpID);
1272
1273    //1. check session
1274    if (null == this.session) {
1275      throw new SecurityException("session not set");
1276    }
1277
1278    if (false == this.ac.isValidSession(this.session)) {
1279      throw new SecurityException("invalid session supplied");
1280    }
1281
1282    //2. check permissions
1283    if (false == canWriteLR(corpID)) {
1284      throw new SecurityException("no write access granted to the user");
1285    }
1286
1287    if (false == canWriteLR(docID)) {
1288      throw new SecurityException("no write access granted to the user");
1289    }
1290
1291    //3. database
1292    PreparedStatement pstmt = null;
1293
1294    try {
1295      pstmt = this.jdbcConn.prepareStatement("select persist_add_document_to_corpus(?,?) ");
1296      pstmt.setLong(1,docID.longValue());
1297      pstmt.setLong(2,corpID.longValue());
1298      pstmt.execute();
1299    }
1300    catch(SQLException sqle) {
1301
1302      switch(sqle.getErrorCode()) {
1303        case DBHelper.X_ORACLE_INVALID_LR:
1304          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
1305        default:
1306          throw new PersistenceException(
1307                "can't add document to corpus : ["+ sqle.getMessage()+"]");
1308      }
1309    }
1310    finally {
1311      DBHelper.cleanup(pstmt);
1312    }
1313  }
1314
1315
1316}