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.32 2002/04/10 16:31:53 marin 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.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       pstmt.setString(2,doc.getSourceUrl().toString());
852       //do we have start offset?
853       if (null==doc.getSourceUrlStartOffset()) {
854         pstmt.setNull(3,java.sql.Types.INTEGER);
855       }
856       else {
857         pstmt.setLong(3,doc.getSourceUrlStartOffset().longValue());
858       }
859       //do we have end offset?
860       if (null==doc.getSourceUrlEndOffset()) {
861         pstmt.setNull(4,java.sql.Types.INTEGER);
862       }
863       else {
864         pstmt.setLong(4,doc.getSourceUrlEndOffset().longValue());
865       }
866 
867       pstmt.setBoolean(5,doc.getMarkupAware().booleanValue());
868       pstmt.execute();
869     }
870     catch(SQLException sqle) {
871 
872       switch(sqle.getErrorCode()) {
873         case DBHelper.X_ORACLE_INVALID_LR :
874           throw new PersistenceException("invalid LR supplied: no such document: ["+
875                                                             sqle.getMessage()+"]");
876         default:
877           throw new PersistenceException("can't change document data: ["+
878                                                             sqle.getMessage()+"]");
879       }
880     }
881     finally {
882       DBHelper.cleanup(pstmt);
883     }
884 
885   }
886 
887   /** helper for sync() - never call directly */
888   protected void _syncDocumentContent(Document doc)
889     throws PersistenceException {
890 
891     //0.
892     Assert.assertNotNull(doc);
893     Assert.assertNotNull(doc.getLRPersistenceId());
894     Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
895 
896     PreparedStatement pstmt = null;
897     //1.
898     try {
899       pstmt = this.jdbcConn.prepareStatement("select persist_update_document_content(?,?)");
900       pstmt.setLong(1,((Long)doc.getLRPersistenceId()).longValue());
901 
902       DocumentContent dc = doc.getContent();
903       if (dc.size().longValue() > 0) {
904         pstmt.setString(2,dc.toString());
905       }
906       else {
907         pstmt.setNull(2,java.sql.Types.LONGVARCHAR);
908       }
909 
910       pstmt.execute();
911     }
912     catch(SQLException sqle) {
913       throw new PersistenceException("Cannot update document content ["+
914                                       sqle.getMessage()+"]");
915     }
916     finally {
917       DBHelper.cleanup(pstmt);
918     }
919   }
920 
921   /** helper for sync() - never call directly */
922   protected void _syncFeatures(LanguageResource lr)
923     throws PersistenceException {
924 
925     //0. preconditions
926     Assert.assertNotNull(lr);
927     Assert.assertNotNull(lr.getLRPersistenceId());
928     Assert.assertEquals(((DatabaseDataStore)lr.getDataStore()).getDatabaseID(),
929                       this.getDatabaseID());
930     Assert.assertTrue(lr instanceof Document || lr instanceof Corpus);
931     //we have to be in the context of transaction
932 
933     //1, get ID  in the DB
934     Long lrID = (Long)lr.getLRPersistenceId();
935     int  entityType;
936 
937     //2. delete features
938     PreparedStatement pstmt = null;
939     try {
940       Assert.assertTrue(false == this.jdbcConn.getAutoCommit());
941       pstmt = this.jdbcConn.prepareStatement("select persist_delete_features(?,?) ");
942       pstmt.setLong(1,lrID.longValue());
943 
944       if (lr instanceof Document) {
945         entityType = DBHelper.FEATURE_OWNER_DOCUMENT;
946       }
947       else if (lr instanceof Corpus) {
948         entityType = DBHelper.FEATURE_OWNER_CORPUS;
949       }
950       else {
951         throw new IllegalArgumentException();
952       }
953 
954       pstmt.setInt(2,entityType);
955       pstmt.execute();
956     }
957     catch(SQLException sqle) {
958       throw new PersistenceException("can't delete features in DB: ["+ sqle.getMessage()+"]");
959     }
960     finally {
961       DBHelper.cleanup(pstmt);
962     }
963 
964     //3. recreate them
965     createFeatures(lrID,entityType, lr.getFeatures());
966   }
967 
968   /** helper for sync() - never call directly */
969 /*  protected void _syncAnnotationSets(Document doc,Collection removedSets,Collection addedSets)
970     throws PersistenceException {
971 
972     throw new MethodNotImplementedException();
973   }
974 */
975 
976   /** helper for sync() - never call directly */
977 /*  protected void _syncAddedAnnotations(Document doc, AnnotationSet as, Collection changes)
978     throws PersistenceException {
979 
980     throw new MethodNotImplementedException();
981   }
982 */
983 
984   /** helper for sync() - never call directly */
985 /*  protected void _syncRemovedAnnotations(Document doc,AnnotationSet as, Collection changes)
986     throws PersistenceException {
987 
988     throw new MethodNotImplementedException();
989   }
990 */
991   /** helper for sync() - never call directly */
992 /*  protected void _syncChangedAnnotations(Document doc,AnnotationSet as, Collection changes)
993     throws PersistenceException {
994 
995     throw new MethodNotImplementedException();
996   }
997 */
998 
999   /**
1000   *  reads the features of an entity
1001   *  entities are of type LR or Annotation
1002   */
1003  protected FeatureMap readFeatures(Long entityID, int entityType)
1004    throws PersistenceException {
1005
1006    //0. preconditions
1007    Assert.assertNotNull(entityID);
1008    Assert.assertTrue(entityType == DBHelper.FEATURE_OWNER_ANNOTATION ||
1009                  entityType == DBHelper.FEATURE_OWNER_CORPUS ||
1010                  entityType == DBHelper.FEATURE_OWNER_DOCUMENT);
1011
1012
1013    PreparedStatement pstmt = null;
1014    ResultSet rs = null;
1015    FeatureMap fm = new SimpleFeatureMapImpl();
1016
1017    //1. read from DB
1018    try {
1019      String sql = " select ftkey.fk_string, " +
1020                   "        ft.ft_value_type, " +
1021                   "        ft.ft_int_value, " +
1022                   "        ft.ft_float_value, " +
1023                   "        ft.ft_binary_value, " +
1024                   "        ft.ft_character_value " +
1025                   " from   t_feature ft, " +
1026                   "        t_feature_key ftkey " +
1027                   " where  ft.ft_entity_id = ? " +
1028                   "        and ft.ft_entity_type = ? " +
1029                   "        and ft.ft_key_id = ftkey.fk_id " +
1030                   " order by ftkey.fk_string,ft.ft_id";
1031
1032      pstmt = this.jdbcConn.prepareStatement(sql);
1033      pstmt.setLong(1,entityID.longValue());
1034      pstmt.setLong(2,entityType);
1035      pstmt.execute();
1036      rs = pstmt.getResultSet();
1037
1038      //3. fill feature map
1039      Vector arrFeatures = new Vector();
1040      String prevKey = null;
1041      String currKey = null;
1042      Object currFeature = null;
1043
1044
1045      while (rs.next()) {
1046        //NOTE: because there are LOBs in the resulset
1047        //the columns should be read in the order they appear
1048        //in the query
1049        currKey = rs.getString("fk_string");
1050
1051        Long valueType = new Long(rs.getLong("ft_value_type"));
1052
1053        //we don't quite know what is the type of the NUMBER
1054        //stored in DB
1055        Object numberValue = null;
1056
1057        //for all numeric types + boolean -> read from DB as appropriate
1058        //Java object
1059        switch(valueType.intValue()) {
1060
1061          case DBHelper.VALUE_TYPE_BOOLEAN:
1062            numberValue = new Boolean(rs.getBoolean("ft_int_value"));
1063            break;
1064
1065          case DBHelper.VALUE_TYPE_FLOAT:
1066            numberValue = new Double(rs.getDouble("ft_float_value"));
1067            break;
1068
1069          case DBHelper.VALUE_TYPE_INTEGER:
1070            numberValue = new Integer(rs.getInt("ft_int_value"));
1071            break;
1072
1073          case DBHelper.VALUE_TYPE_LONG:
1074            numberValue = new Long(rs.getLong("ft_int_value"));
1075            break;
1076        }
1077
1078        //don't forget to read the rest of the current row
1079        InputStream blobValue = rs.getBinaryStream("ft_binary_value");
1080        String stringValue = rs.getString("ft_character_value");
1081
1082        switch(valueType.intValue()) {
1083
1084          case DBHelper.VALUE_TYPE_NULL:
1085            currFeature = null;
1086            break;
1087
1088          case DBHelper.VALUE_TYPE_BOOLEAN:
1089          case DBHelper.VALUE_TYPE_FLOAT:
1090          case DBHelper.VALUE_TYPE_INTEGER:
1091          case DBHelper.VALUE_TYPE_LONG:
1092            currFeature = numberValue;
1093            break;
1094
1095          case DBHelper.VALUE_TYPE_BINARY:
1096            //deserialize a java object
1097            ObjectInputStream ois = new ObjectInputStream(blobValue);
1098            currFeature = ois.readObject();
1099            ois.close();
1100            blobValue.close();
1101            break;
1102
1103          case DBHelper.VALUE_TYPE_STRING:
1104            currFeature = stringValue;
1105            break;
1106
1107          default:
1108            throw new PersistenceException("Invalid feature type found in DB, type is ["+valueType.intValue()+"]");
1109        }//switch
1110
1111        //new feature or part of an array?
1112        if (currKey.equals(prevKey) && prevKey != null) {
1113          //part of array
1114          arrFeatures.add(currFeature);
1115        }
1116        else {
1117          //add prev feature to feature map
1118
1119          //is the prev feature an array or a single object?
1120          if (arrFeatures.size() > 1) {
1121            //put a clone, because this is a temp array that will
1122            //be cleared in few lines
1123            fm.put(prevKey, new Vector(arrFeatures));
1124          }
1125          else if (arrFeatures.size() == 1) {
1126            fm.put(prevKey,arrFeatures.elementAt(0));
1127          }
1128          else {
1129            //do nothing, this is the dummy feature
1130            ;
1131          }//if
1132
1133          //now clear the array from previous fesature(s) and put the new
1134          //one there
1135          arrFeatures.clear();
1136
1137          prevKey = currKey;
1138          arrFeatures.add(currFeature);
1139        }//if
1140      }//while
1141
1142      //add the last feature
1143      if (arrFeatures.size() > 1) {
1144        fm.put(currKey,arrFeatures);
1145      }
1146      else if (arrFeatures.size() == 1) {
1147        fm.put(currKey,arrFeatures.elementAt(0));
1148      }
1149    }//try
1150    catch(SQLException sqle) {
1151      throw new PersistenceException("can't read features from DB: ["+ sqle.getMessage()+"]");
1152    }
1153    catch(IOException ioe) {
1154      throw new PersistenceException("can't read features from DB: ["+ ioe.getMessage()+"]");
1155    }
1156    catch(ClassNotFoundException cnfe) {
1157      throw new PersistenceException("can't read features from DB: ["+ cnfe.getMessage()+"]");
1158    }
1159    finally {
1160      DBHelper.cleanup(rs);
1161      DBHelper.cleanup(pstmt);
1162    }
1163
1164    return fm;
1165  }
1166
1167
1168  /**
1169   *  helper method for delete()
1170   *  never call it directly beause proper events will not be fired
1171   */
1172  protected void deleteDocument(Long lrId)
1173  throws PersistenceException {
1174    //0. preconditions
1175    Assert.assertNotNull(lrId);
1176
1177    PreparedStatement pstmt = null;
1178
1179    //1. delete from DB
1180    try {
1181      pstmt = this.jdbcConn.prepareStatement("select persist_delete_document(?) ");
1182      pstmt.setLong(1,lrId.longValue());
1183      pstmt.execute();
1184    }
1185    catch(SQLException sqle) {
1186      throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
1187    }
1188    finally {
1189      DBHelper.cleanup(pstmt);
1190    }
1191  }
1192
1193  /**
1194   *  helper method for delete()
1195   *  never call it directly beause proper events will not be fired
1196   */
1197  protected void deleteCorpus(Long lrId)
1198    throws PersistenceException {
1199
1200    Long ID = (Long)lrId;
1201
1202    PreparedStatement pstmt = null;
1203
1204    try {
1205      pstmt = this.jdbcConn.prepareStatement("select persist_delete_corpus(?)");
1206      pstmt.setLong(1,ID.longValue());
1207      pstmt.execute();
1208    }
1209    catch(SQLException sqle) {
1210      throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
1211    }
1212    finally {
1213      DBHelper.cleanup(pstmt);
1214    }
1215  }
1216
1217
1218  /** helper for sync() - never call directly */
1219  protected void _syncRemovedDocumentsFromCorpus(List docLRIDs, Long corpLRID)
1220    throws PersistenceException {
1221
1222    //0.preconditions
1223    Assert.assertNotNull(docLRIDs);
1224    Assert.assertNotNull(corpLRID);
1225    Assert.assertTrue(docLRIDs.size() > 0);
1226
1227    PreparedStatement pstmt = null;
1228
1229    try {
1230      pstmt = this.jdbcConn.prepareStatement("select persist_remove_doc_from_corpus(?,?)");
1231
1232      Iterator it = docLRIDs.iterator();
1233      while (it.hasNext()) {
1234        Long currLRID = (Long)it.next();
1235        pstmt.setLong(1,currLRID.longValue());
1236        pstmt.setLong(2,corpLRID.longValue());
1237        pstmt.execute();
1238      }
1239    }
1240    catch(SQLException sqle) {
1241
1242      switch(sqle.getErrorCode()) {
1243        case DBHelper.X_ORACLE_INVALID_LR :
1244          throw new PersistenceException("invalid LR supplied: no such document: ["+
1245                                                            sqle.getMessage()+"]");
1246        default:
1247          throw new PersistenceException("can't change document data: ["+
1248                                                            sqle.getMessage()+"]");
1249      }
1250    }
1251    finally {
1252      DBHelper.cleanup(pstmt);
1253    }
1254
1255  }
1256
1257  /**
1258   *   adds document to corpus in the database
1259   *   if the document is already part of the corpus nothing
1260   *   changes
1261   */
1262  protected void addDocumentToCorpus(Long docID,Long corpID)
1263  throws PersistenceException,SecurityException {
1264
1265    //0. preconditions
1266    Assert.assertNotNull(docID);
1267    Assert.assertNotNull(corpID);
1268
1269    //1. check session
1270    if (null == this.session) {
1271      throw new SecurityException("session not set");
1272    }
1273
1274    if (false == this.ac.isValidSession(this.session)) {
1275      throw new SecurityException("invalid session supplied");
1276    }
1277
1278    //2. check permissions
1279    if (false == canWriteLR(corpID)) {
1280      throw new SecurityException("no write access granted to the user");
1281    }
1282
1283    if (false == canWriteLR(docID)) {
1284      throw new SecurityException("no write access granted to the user");
1285    }
1286
1287    //3. database
1288    PreparedStatement pstmt = null;
1289
1290    try {
1291      pstmt = this.jdbcConn.prepareStatement("select persist_add_document_to_corpus(?,?) ");
1292      pstmt.setLong(1,docID.longValue());
1293      pstmt.setLong(2,corpID.longValue());
1294      pstmt.execute();
1295    }
1296    catch(SQLException sqle) {
1297
1298      switch(sqle.getErrorCode()) {
1299        case DBHelper.X_ORACLE_INVALID_LR:
1300          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
1301        default:
1302          throw new PersistenceException(
1303                "can't add document to corpus : ["+ sqle.getMessage()+"]");
1304      }
1305    }
1306    finally {
1307      DBHelper.cleanup(pstmt);
1308    }
1309  }
1310
1311
1312}