1   /*
2    *  OracleDataStore.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/Sep/2001
12   *
13   *  $Id: OracleDataStore.java,v 1.146 2002/03/07 19:41:03 marin Exp $
14   */
15  
16  package gate.persist;
17  
18  import java.sql.*;
19  import java.net.*;
20  import java.util.*;
21  import java.io.*;
22  
23  import oracle.sql.*;
24  import oracle.jdbc.driver.*;
25  
26  
27  import junit.framework.*;
28  
29  import gate.*;
30  import gate.util.*;
31  import gate.event.*;
32  import gate.security.*;
33  import gate.security.SecurityException; //hide the more general exception
34  import gate.corpora.*;
35  import gate.annotation.*;
36  import gate.creole.ResourceData;
37  
38  public class OracleDataStore extends JDBCDataStore {
39  
40    /** Name of this resource */
41    public static final String DS_COMMENT = "GATE Oracle datastore";
42  
43    /** the icon for this resource */
44    public static final String DS_ICON_NAME = "ora_ds.gif";
45  
46    /** Debug flag */
47    private static final boolean DEBUG = false;
48  
49    /** "true" value for Oracle (supports no boolean type) */
50    private static final int ORACLE_TRUE = 1;
51    /** "false" value for Oracle (supports no boolean type) */
52    private static final int ORACLE_FALSE = 0;
53  
54    /** used internaly, may change in the future */
55    private static final int READ_ACCESS = 0;
56    /** used internaly, may change in the future */
57    private static final int WRITE_ACCESS = 1;
58  
59    /** size of the Oracle varrays used for bulc inserts */
60    private static final int VARRAY_SIZE = 10;
61  
62    /** the size in bytes if varchar2 column in Oracle
63     *  when a String is stored in Oracle it may be too long
64     *  for a varchar2 value, and then CLOB will be used
65     *  Note that the limit is in bytes, not in characters, so
66     *  in the worst case this will limit the string to 4000/3 characters
67     *  */
68    private static final int ORACLE_VARCHAR_LIMIT_BYTES = 4000;
69  
70    /** maximum number of bytes that represent a char in UTF8 database */
71    private static final int UTF_BYTES_PER_CHAR_MAX = 3;
72  
73    /** maximum number of characters per string stored as varchar2
74     *  if longer then stored as CLOB
75     *   */
76    private static final int ORACLE_VARCHAR_MAX_SYMBOLS =
77                                    ORACLE_VARCHAR_LIMIT_BYTES/UTF_BYTES_PER_CHAR_MAX;
78  
79    /** read buffer size (for reading CLOBs) */
80    private static final int INTERNAL_BUFFER_SIZE = 16*1024;
81  
82    /** default constructor - just call the super constructor
83     *  (may change in the future)
84     *  */
85    public OracleDataStore() {
86  
87      super();
88    }
89  
90    /**
91     * Save: synchonise the in-memory image of the LR with the persistent
92     * image.
93     */
94    public String getComment() {
95      return OracleDataStore.DS_COMMENT;
96    }
97  
98    /**
99     * Returns the name of the icon to be used when this datastore is displayed
100    * in the GUI
101    */
102   public String getIconName() {
103     return OracleDataStore.DS_ICON_NAME;
104   }
105 
106 
107 
108   /** Get the name of an LR from its ID. */
109   public String getLrName(Object lrId)
110     throws PersistenceException {
111 
112     if (false == lrId instanceof Long) {
113       throw new IllegalArgumentException();
114     }
115 
116     Long ID = (Long)lrId;
117 
118     CallableStatement stmt = null;
119 
120     try {
121       stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.get_lr_name(?,?) }");
122       stmt.setLong(1,ID.longValue());
123       stmt.registerOutParameter(2,java.sql.Types.VARCHAR);
124       stmt.execute();
125       String result = stmt.getString(2);
126 
127       return result;
128     }
129     catch(SQLException sqle) {
130       throw new PersistenceException("can't get LR name from DB: ["+ sqle.getMessage()+"]");
131     }
132     finally {
133       DBHelper.cleanup(stmt);
134     }
135   }
136 
137 
138 
139   /** Set the URL for the underlying storage mechanism. */
140   public void setStorageUrl(String storageUrl) throws PersistenceException {
141 
142     super.setStorageUrl(storageUrl);
143 
144   }
145 
146 
147 
148   /** Get the URL for the underlying storage mechanism. */
149   public String getStorageUrl() {
150 
151     return super.getStorageUrl();
152   }
153 
154 
155 
156   /**
157    * Create a new data store. <B>NOTE:</B> for some data stores
158    * creation is an system administrator task; in such cases this
159    * method will throw an UnsupportedOperationException.
160    */
161   public void create()
162   throws PersistenceException, UnsupportedOperationException {
163 
164     super.create();
165   }
166 
167 
168 
169   /** Open a connection to the data store. */
170   public void open() throws PersistenceException {
171 
172     super.open();
173 
174     try {
175     //set statement caching for Oracle
176       ((OracleConnection)this.jdbcConn).setStmtCacheSize(50);
177     }
178     catch(SQLException sqle) {
179       throw new PersistenceException(sqle);
180     }
181   }
182 
183 
184 
185   /** Close the data store. */
186   public void close() throws PersistenceException {
187 
188     super.close();
189   }
190 
191 
192 
193   /**
194    * Delete the data store. <B>NOTE:</B> for some data stores
195    * deletion is an system administrator task; in such cases this
196    * method will throw an UnsupportedOperationException.
197    */
198   public void delete()
199   throws PersistenceException, UnsupportedOperationException {
200 
201     //0. user session should be set
202 /*    if (null == this.session) {
203       throw new SecurityException("user session not set");
204     }
205 */
206     super.delete();
207   }
208 
209 
210 
211   /**
212    * Delete a resource from the data store.
213    * @param lrId a data-store specific unique identifier for the resource
214    * @param lrClassName class name of the type of resource
215    */
216   public void delete(String lrClassName, Object lrId)
217   throws PersistenceException,SecurityException {
218     //0. preconditions
219     if (false == lrId instanceof Long) {
220       throw new IllegalArgumentException();
221     }
222 
223     if (!lrClassName.equals(DBHelper.DOCUMENT_CLASS) &&
224         !lrClassName.equals(DBHelper.CORPUS_CLASS)) {
225       throw new IllegalArgumentException("Only Corpus and Document classes are supported" +
226                                           " by Database data store");
227     }
228 
229     //1. check session
230     if (null == this.session) {
231       throw new SecurityException("session not set");
232     }
233 
234     if (false == this.ac.isValidSession(this.session)) {
235       throw new SecurityException("invalid session supplied");
236     }
237 
238     //2. check permissions
239     if (false == canWriteLR(lrId)) {
240       throw new SecurityException("insufficient privileges");
241     }
242 
243     //3. try to lock document, so that we'll be sure no one is editing it
244     //NOTE: use the private method
245     User lockingUser = this.getLockingUser((Long)lrId);
246     User currUser = this.session.getUser();
247 
248     if (null != lockingUser && false == lockingUser.equals(currUser)) {
249       //oops, someone is editing now
250       throw new PersistenceException("LR locked by another user");
251     }
252 
253     boolean transFailed = false;
254     try {
255       //4. autocommit should be FALSE because of LOBs
256 //      this.jdbcConn.setAutoCommit(false);
257       beginTrans();
258 
259       //5. perform changes, if anything goes wrong, rollback
260       if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
261         deleteDocument((Long)lrId);
262       }
263       else {
264         deleteCorpus((Long)lrId);
265       }
266 
267       //6. done, commit
268 //      this.jdbcConn.commit();
269       commitTrans();
270     }
271 //    catch(SQLException sqle) {
272 //      transFailed = true;
273 //      throw new PersistenceException("Cannot start/commit a transaction, ["+sqle.getMessage()+"]");
274 //    }
275     catch(PersistenceException pe) {
276       transFailed = true;
277       throw(pe);
278     }
279     finally {
280       //problems?
281       if (transFailed) {
282 //          this.jdbcConn.rollback();
283         rollbackTrans();
284       }
285     }
286 
287     //7, unlock
288     //do nothing - the resource does not exist anymore
289 
290     //8. delete from the list of dependent resources
291     boolean resourceFound = false;
292     Iterator it = this.dependentResources.iterator();
293     while (it.hasNext()) {
294       LanguageResource lr = (LanguageResource)it.next();
295       if (lr.getLRPersistenceId().equals(lrId)) {
296         resourceFound = true;
297         it.remove();
298         break;
299       }
300     }
301 
302     //Assert.assertTrue(resourceFound);
303 
304     //9. let the world know about it
305     fireResourceDeleted(
306       new DatastoreEvent(this, DatastoreEvent.RESOURCE_DELETED, null, lrId));
307 
308     //10. unload the resource form the GUI
309     try {
310       unloadLR((Long)lrId);
311     }
312     catch(GateException ge) {
313       Err.prln("can't unload resource from GUI...");
314     }
315   }
316 
317 
318 
319   /**
320    *  helper method for delete()
321    *  never call it directly beause proper events will not be fired
322    */
323   private void deleteDocument(Long lrId)
324   throws PersistenceException {
325 
326     //0. preconditions
327     Assert.assertNotNull(lrId);
328 
329     CallableStatement stmt = null;
330 
331     //1. delete from DB
332     try {
333       stmt = this.jdbcConn.prepareCall(
334                       "{ call "+Gate.DB_OWNER+".persist.delete_document(?) }");
335       stmt.setLong(1,lrId.longValue());
336       stmt.execute();
337     }
338     catch(SQLException sqle) {
339       throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
340     }
341     finally {
342       DBHelper.cleanup(stmt);
343     }
344   }
345 
346 
347 
348   /**
349    *  helper method for delete()
350    *  never call it directly beause proper events will not be fired
351    */
352   private void deleteCorpus(Long lrId)
353   throws PersistenceException {
354 
355     Long ID = (Long)lrId;
356 
357     CallableStatement stmt = null;
358 
359     try {
360       stmt = this.jdbcConn.prepareCall(
361                       "{ call "+Gate.DB_OWNER+".persist.delete_corpus(?) }");
362       stmt.setLong(1,ID.longValue());
363       stmt.execute();
364     }
365     catch(SQLException sqle) {
366       throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
367     }
368     finally {
369       DBHelper.cleanup(stmt);
370     }
371   }
372 
373 
374 
375   /**
376    * Save: synchonise the in-memory image of the LR with the persistent
377    * image.
378    */
379   public void sync(LanguageResource lr)
380   throws PersistenceException,SecurityException {
381 
382     //4.delegate (open a new transaction)
383     _sync(lr,true);
384   }
385 
386 
387 
388   /**
389    * Save: synchonise the in-memory image of the LR with the persistent
390    * image.
391    */
392   private void _sync(LanguageResource lr, boolean openNewTrans)
393     throws PersistenceException,SecurityException {
394 
395     //0.preconditions
396     Assert.assertNotNull(lr);
397     Long lrID = (Long)lr.getLRPersistenceId();
398 
399     if (false == lr instanceof Document &&
400         false == lr instanceof Corpus) {
401       //only documents and corpuses could be serialized in DB
402       throw new IllegalArgumentException("only Documents and Corpuses could "+
403                                           "be serialized in DB");
404     }
405 
406     // check that this LR is one of ours (i.e. has been adopted)
407     if( null == lr.getDataStore() || false == lr.getDataStore().equals(this))
408       throw new PersistenceException(
409         "This LR is not stored in this DataStore"
410       );
411 
412 
413     //1. check session
414     if (null == this.session) {
415       throw new SecurityException("session not set");
416     }
417 
418     if (false == this.ac.isValidSession(this.session)) {
419       throw new SecurityException("invalid session supplied");
420     }
421 
422     //2. check permissions
423     if (false == canWriteLR(lrID)) {
424       throw new SecurityException("insufficient privileges");
425     }
426 
427     //3. is the resource locked?
428     User lockingUser = getLockingUser(lr);
429     User currUser = this.session.getUser();
430 
431     if (lockingUser != null && false == lockingUser.equals(currUser)) {
432       throw new PersistenceException("document is locked by another user and cannot be synced");
433     }
434 
435 
436     boolean transFailed = false;
437     try {
438       //2. autocommit should be FALSE because of LOBs
439       if (openNewTrans) {
440 //        this.jdbcConn.setAutoCommit(false);
441         beginTrans();
442       }
443 
444       //3. perform changes, if anything goes wrong, rollback
445       if (lr instanceof Document) {
446         syncDocument((Document)lr);
447       }
448       else {
449         syncCorpus((Corpus)lr);
450       }
451 
452       //4. done, commit
453       if (openNewTrans) {
454 //        this.jdbcConn.commit();
455         commitTrans();
456       }
457     }
458     catch(PersistenceException pe) {
459       transFailed = true;
460       throw(pe);
461     }
462     finally {
463       //problems?
464       if (transFailed) {
465         rollbackTrans();
466   //      try {
467 //          this.jdbcConn.rollback();
468 //        }
469 //        catch(SQLException sqle) {
470 //          throw new PersistenceException(sqle);
471 //        }
472       }
473     }
474 
475     // let the world know about it
476     fireResourceWritten(
477       new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN, lr, lr.getLRPersistenceId()));
478 
479   }
480 
481 
482 
483   /**
484    * Set method for the autosaving behaviour of the data store.
485    * <B>NOTE:</B> many types of datastore have no auto-save function,
486    * in which case this will throw an UnsupportedOperationException.
487    */
488   public void setAutoSaving(boolean autoSaving)
489   throws UnsupportedOperationException,PersistenceException {
490 
491     super.setAutoSaving(autoSaving);
492   }
493 
494 
495 
496   /** Get the autosaving behaviour of the LR. */
497   public boolean isAutoSaving() {
498     throw new MethodNotImplementedException();
499   }
500 
501 
502   /** Adopt a resource for persistence. */
503   public LanguageResource adopt(LanguageResource lr, SecurityInfo secInfo)
504   throws PersistenceException,SecurityException {
505     //open a new transaction
506     return _adopt(lr,secInfo,true);
507   }
508 
509   /** helper for adopt()
510    *  @param openNewTrans shows if a new transaction should be started or the adopt
511    *  is performed in the context of an already opened transaction
512    */
513   private LanguageResource _adopt(LanguageResource lr,
514                                   SecurityInfo secInfo,
515                                   boolean openNewTrans)
516   throws PersistenceException,SecurityException {
517 
518     LanguageResource result = null;
519 
520     //-1. preconditions
521     Assert.assertNotNull(lr);
522     Assert.assertNotNull(secInfo);
523     if (false == lr instanceof Document &&
524         false == lr instanceof Corpus) {
525       //only documents and corpuses could be serialized in DB
526       throw new IllegalArgumentException("only Documents and Corpuses could "+
527                                           "be serialized in DB");
528     }
529 
530     //0. check SecurityInfo
531     if (false == this.ac.isValidSecurityInfo(secInfo)) {
532       throw new SecurityException("Invalid security settings supplied");
533     }
534 
535     //1. user session should be set
536     if (null == this.session) {
537       throw new SecurityException("user session not set");
538     }
539 
540     //2. check the LR's current DS
541     DataStore currentDS = lr.getDataStore();
542     if(currentDS == null) {
543       // an orphan - do the adoption (later)
544     }
545     else if(currentDS.equals(this)){         // adopted already
546       return lr;
547     }
548     else {                      // someone else's child
549       throw new PersistenceException(
550         "Can't adopt a resource which is already in a different datastore");
551     }
552 
553 
554     //3. is the LR one of Document or Corpus?
555     if (false == lr instanceof Document &&
556         false == lr instanceof Corpus) {
557 
558       throw new IllegalArgumentException("Database datastore is implemented only for "+
559                                         "Documents and Corpora");
560     }
561 
562     //4.is the document already stored in this storage?
563     Object persistID = lr.getLRPersistenceId();
564     if (persistID != null) {
565       throw new PersistenceException("This LR is already stored in the " +
566                                       " database (persistance ID is =["+(Long)persistID+"] )");
567     }
568 
569     boolean transFailed = false;
570     try {
571       //5 autocommit should be FALSE because of LOBs
572       if (openNewTrans) {
573 //        this.jdbcConn.setAutoCommit(false);
574         beginTrans();
575       }
576 
577       //6. perform changes, if anything goes wrong, rollback
578       if (lr instanceof Document) {
579         result =  createDocument((Document)lr,secInfo);
580 //System.out.println("result ID=["+result.getLRPersistenceId()+"]");
581       }
582       else {
583         //adopt each document from the corpus in a separate transaction context
584         result =  createCorpus((Corpus)lr,secInfo,true);
585       }
586 
587       //7. done, commit
588       if (openNewTrans) {
589 //        this.jdbcConn.commit();
590         commitTrans();
591       }
592     }
593 /*
594     catch(SQLException sqle) {
595       transFailed = true;
596       throw new PersistenceException("Cannot start/commit a transaction, ["+sqle.getMessage()+"]");
597     }
598 */
599     catch(PersistenceException pe) {
600       transFailed = true;
601       throw(pe);
602     }
603     catch(SecurityException se) {
604       transFailed = true;
605       throw(se);
606     }
607     finally {
608       //problems?
609       if (transFailed) {
610         rollbackTrans();
611 /*        try {
612           this.jdbcConn.rollback();
613         }
614         catch(SQLException sqle) {
615           throw new PersistenceException(sqle);
616         }
617 */
618       }
619     }
620 
621     //8. let the world know
622     fireResourceAdopted(
623         new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
624                            result,
625                            result.getLRPersistenceId())
626     );
627 
628     //9. fire also resource written event because it's now saved
629     fireResourceWritten(
630       new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
631                           result,
632                           result.getLRPersistenceId()
633       )
634     );
635 
636     //10. add the resource to the list of dependent resources - i.e. the ones that the
637     //data store should take care upon closing [and call sync()]
638     this.dependentResources.add(result);
639 
640     return result;
641   }
642 
643 
644 
645   /**
646    *  helper for adopt()
647    *  never call directly
648    */
649   private Long createLR(String lrType,
650                         String lrName,
651                         SecurityInfo si,
652                         Long lrParentID)
653   throws PersistenceException,SecurityException {
654 
655     //0. preconditions
656     Assert.assertNotNull(lrName);
657 
658     //1. check the session
659 //    if (this.ac.isValidSession(s) == false) {
660 //      throw new SecurityException("invalid session provided");
661 //    }
662 
663     //2. create a record in DB
664     CallableStatement stmt = null;
665 
666     try {
667       stmt = this.jdbcConn.prepareCall(
668                     "{ call "+Gate.DB_OWNER+".persist.create_lr(?,?,?,?,?,?,?) }");
669       stmt.setLong(1,si.getUser().getID().longValue());
670       stmt.setLong(2,si.getGroup().getID().longValue());
671       stmt.setString(3,lrType);
672       stmt.setString(4,lrName);
673       stmt.setInt(5,si.getAccessMode());
674       if (null == lrParentID) {
675         stmt.setNull(6,java.sql.Types.BIGINT);
676       }
677       else {
678         stmt.setLong(6,lrParentID.longValue());
679       }
680       //Oracle numbers are BIGNINT
681       stmt.registerOutParameter(7,java.sql.Types.BIGINT);
682       stmt.execute();
683 
684       Long result =  new Long(stmt.getLong(7));
685       return result;
686     }
687     catch(SQLException sqle) {
688 
689       switch(sqle.getErrorCode()) {
690         case DBHelper.X_ORACLE_INVALID_LR_TYPE:
691           throw new PersistenceException("can't create LR [step 3] in DB, invalid LR Type");
692         default:
693           throw new PersistenceException(
694                 "can't create LR [step 3] in DB : ["+ sqle.getMessage()+"]");
695       }
696     }
697     finally {
698       DBHelper.cleanup(stmt);
699     }
700   }
701 
702 
703 
704   /**
705    *  updates the content of the document if it is binary or a long string
706    *  (that does not fit into VARCHAR2)
707    */
708   private void updateDocumentContent(Long docContentID,DocumentContent content)
709   throws PersistenceException {
710 
711     //1. get LOB locators from DB
712     PreparedStatement pstmt = null;
713     ResultSet rs = null;
714     CallableStatement cstmt = null;
715     try {
716       String sql =  "select dc_content_type, " +
717                     "       dc_character_content, " +
718                     "       dc_binary_content " +
719                     "from "+gate.Gate.DB_OWNER+".t_doc_content " +
720                     "where  dc_id = ? " +
721                     "for update ";
722       pstmt = this.jdbcConn.prepareStatement(sql);
723       pstmt.setLong(1,docContentID.longValue());
724       rs = pstmt.executeQuery();
725 
726       //rs = pstmt.getResultSet();
727 
728       rs.next();
729       //important: read the objects in the order they appear in
730       //the ResultSet, otherwise data may be lost
731       long contentType = rs.getLong("DC_CONTENT_TYPE");
732       Clob clob = (Clob)rs.getClob("dc_character_content");
733       Blob blob = (Blob)rs.getBlob("dc_binary_content");
734 
735       Assert.assertTrue(contentType == DBHelper.CHARACTER_CONTENT ||
736                     contentType == DBHelper.BINARY_CONTENT ||
737                     contentType == DBHelper.EMPTY_CONTENT);
738 
739 
740       //2. write data using the LOB locators
741       //NOTE: so far only character content is supported
742       writeCLOB(content.toString(),clob);
743       long newContentType = DBHelper.CHARACTER_CONTENT;
744 
745       //3. update content type
746       cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.change_content_type(?,?) }");
747       cstmt.setLong(1,docContentID.longValue());
748       cstmt.setLong(2,newContentType);
749       cstmt.execute();
750     }
751     catch(IOException ioe) {
752       throw new PersistenceException("can't update document content in DB : ["+
753                                       ioe.getMessage()+"]");
754     }
755     catch(SQLException sqle) {
756       throw new PersistenceException("can't update document content in DB : ["+
757                                       sqle.getMessage()+"]");
758     }
759     finally {
760       DBHelper.cleanup(rs);
761       DBHelper.cleanup(pstmt);
762       DBHelper.cleanup(cstmt);
763     }
764 
765   }
766 
767 
768 
769   /**
770    * helper for adopt
771    * creates a LR of type Document
772    */
773   private Document createDocument(Document doc,SecurityInfo secInfo)
774   throws PersistenceException,SecurityException {
775 
776     //delegate, set to Null
777     return createDocument(doc,null,secInfo);
778   }
779 
780 
781 
782   /**
783    * helper for adopt
784    * creates a LR of type Document
785    */
786   private Document createDocument(Document doc, Long corpusID,SecurityInfo secInfo)
787   throws PersistenceException,SecurityException {
788 
789     //-1. preconditions
790     Assert.assertNotNull(doc);
791     Assert.assertNotNull(secInfo);
792 
793     //0. check securoity settings
794     if (false == this.ac.isValidSecurityInfo(secInfo)) {
795       throw new SecurityException("Invalid security settings");
796     }
797 
798     //1. get the data to be stored
799     AnnotationSet defaultAnnotations = doc.getAnnotations();
800     DocumentContent docContent = doc.getContent();
801     FeatureMap docFeatures = doc.getFeatures();
802     String docName  = doc.getName();
803     URL docURL = doc.getSourceUrl();
804     Boolean docIsMarkupAware = doc.getMarkupAware();
805     Long docStartOffset = doc.getSourceUrlStartOffset();
806     Long docEndOffset = doc.getSourceUrlEndOffset();
807     String docEncoding = null;
808     try {
809       docEncoding = (String)doc.
810         getParameterValue(Document.DOCUMENT_ENCODING_PARAMETER_NAME);
811     }
812     catch(gate.creole.ResourceInstantiationException re) {
813       throw new PersistenceException("cannot create document: error getting " +
814                                      " document encoding ["+re.getMessage()+"]");
815     }
816 
817 
818     //3. create a Language Resource (an entry in T_LANG_RESOURCE) for this document
819     Long lrID = createLR(DBHelper.DOCUMENT_CLASS,docName,secInfo,null);
820 
821     //4. create a record in T_DOCUMENT for this document
822     CallableStatement stmt = null;
823     Long docID = null;
824     Long docContentID = null;
825 
826     try {
827       stmt = this.jdbcConn.prepareCall(
828               "{ call "+Gate.DB_OWNER+".persist.create_document(?,?,?,?,?,?,?,?,?) }");
829       stmt.setLong(1,lrID.longValue());
830       stmt.setString(2,docURL.toString());
831       //do we have doc encoding?
832       if (null == docEncoding) {
833         stmt.setNull(3,java.sql.Types.VARCHAR);
834       }
835       else {
836         stmt.setString(3,docEncoding);
837       }
838       //do we have start offset?
839       if (null==docStartOffset) {
840         stmt.setNull(4,java.sql.Types.NUMERIC);
841       }
842       else {
843         stmt.setLong(4,docStartOffset.longValue());
844       }
845       //do we have end offset?
846       if (null==docEndOffset) {
847         stmt.setNull(5,java.sql.Types.NUMERIC);
848       }
849       else {
850         stmt.setLong(5,docEndOffset.longValue());
851       }
852 
853       stmt.setBoolean(6,docIsMarkupAware.booleanValue());
854 
855       //is the document part of a corpus?
856       if (null == corpusID) {
857         stmt.setNull(7,java.sql.Types.BIGINT);
858       }
859       else {
860         stmt.setLong(7,corpusID.longValue());
861       }
862 
863       //results
864       stmt.registerOutParameter(8,java.sql.Types.BIGINT);
865       stmt.registerOutParameter(9,java.sql.Types.BIGINT);
866 
867       stmt.execute();
868       docID = new Long(stmt.getLong(8));
869       docContentID = new Long(stmt.getLong(9));
870     }
871     catch(SQLException sqle) {
872       throw new PersistenceException("can't create document [step 4] in DB: ["+ sqle.getMessage()+"]");
873     }
874     finally {
875       DBHelper.cleanup(stmt);
876     }
877 
878     //5. fill document content (record[s] in T_DOC_CONTENT)
879 
880     //do we have content at all?
881     if (docContent.size().longValue() > 0) {
882       updateDocumentContent(docContentID,docContent);
883     }
884 
885     //6. insert annotations, etc
886 
887     //6.1. create default annotation set
888     createAnnotationSet(lrID,defaultAnnotations);
889 
890     //6.2. create named annotation sets
891     Map namedAnns = doc.getNamedAnnotationSets();
892     //the map may be null
893     if (null != namedAnns) {
894       Set setAnns = namedAnns.entrySet();
895       Iterator itAnns = setAnns.iterator();
896 
897       while (itAnns.hasNext()) {
898         Map.Entry mapEntry = (Map.Entry)itAnns.next();
899         //String currAnnName = (String)mapEntry.getKey();
900         AnnotationSet currAnnSet = (AnnotationSet)mapEntry.getValue();
901 
902         //create a-sets
903         createAnnotationSet(lrID,currAnnSet);
904       }
905     }
906 
907     //7. create features
908 //    createFeatures(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
909     createFeaturesBulk(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
910 
911     //9. create a DatabaseDocument wrapper and return it
912 
913 /*    Document dbDoc = new DatabaseDocumentImpl(this.jdbcConn,
914                                               doc.getName(),
915                                               this,
916                                               lrID,
917                                               doc.getContent(),
918                                               doc.getFeatures(),
919                                               doc.getMarkupAware(),
920                                               doc.getSourceUrl(),
921                                               doc.getSourceUrlStartOffset(),
922                                               doc.getSourceUrlEndOffset(),
923                                               doc.getAnnotations(),
924                                               doc.getNamedAnnotationSets());
925 */
926     Document dbDoc = null;
927     FeatureMap params = Factory.newFeatureMap();
928 
929     HashMap initData = new HashMap();
930     initData.put("JDBC_CONN",this.jdbcConn);
931     initData.put("DS",this);
932     initData.put("LR_ID",lrID);
933     initData.put("DOC_NAME",doc.getName());
934     initData.put("DOC_CONTENT",doc.getContent());
935     initData.put("DOC_FEATURES",doc.getFeatures());
936     initData.put("DOC_MARKUP_AWARE",doc.getMarkupAware());
937     initData.put("DOC_SOURCE_URL",doc.getSourceUrl());
938     initData.put("DOC_SOURCE_URL_START",doc.getSourceUrlStartOffset());
939     initData.put("DOC_SOURCE_URL_END",doc.getSourceUrlEndOffset());
940     initData.put("DOC_DEFAULT_ANNOTATIONS",doc.getAnnotations());
941     initData.put("DOC_NAMED_ANNOTATION_SETS",doc.getNamedAnnotationSets());
942 
943     params.put("initData__$$__", initData);
944 
945     try {
946       //here we create the persistent LR via Factory, so it's registered
947       //in GATE
948       dbDoc = (Document)Factory.createResource("gate.corpora.DatabaseDocumentImpl", params);
949     }
950     catch (gate.creole.ResourceInstantiationException ex) {
951       throw new GateRuntimeException(ex.getMessage());
952     }
953 
954     return dbDoc;
955   }
956 
957 
958 
959   /** creates an entry for annotation set in the database */
960   private void createAnnotationSet(Long lrID, AnnotationSet aset)
961     throws PersistenceException {
962 
963     //1. create a-set
964     String asetName = aset.getName();
965     Long asetID = null;
966 
967     //DB stuff
968     CallableStatement stmt = null;
969     try {
970       stmt = this.jdbcConn.prepareCall(
971                     "{ call "+Gate.DB_OWNER+".persist.create_annotation_set(?,?,?) }");
972       stmt.setLong(1,lrID.longValue());
973 
974       if (null == asetName) {
975         stmt.setNull(2,java.sql.Types.VARCHAR);
976       }
977       else {
978         stmt.setString(2,asetName);
979       }
980       stmt.registerOutParameter(3,java.sql.Types.BIGINT);
981       stmt.execute();
982 
983       asetID = new Long(stmt.getLong(3));
984     }
985     catch(SQLException sqle) {
986       throw new PersistenceException("can't create a-set [step 1] in DB: ["+ sqle.getMessage()+"]");
987     }
988     finally {
989       DBHelper.cleanup(stmt);
990     }
991 
992 
993     //2. insert annotations/nodes for DEFAULT a-set
994     //for now use a stupid cycle
995     //TODO: pass all the data with one DB call (?)
996 
997     try {
998       stmt = this.jdbcConn.prepareCall(
999                 "{ call "+Gate.DB_OWNER+".persist.create_annotation(?,?,?,?,?,?,?,?,?) }");
1000
1001      Iterator itAnnotations = aset.iterator();
1002
1003      while (itAnnotations.hasNext()) {
1004        Annotation ann = (Annotation)itAnnotations.next();
1005        Node start = (Node)ann.getStartNode();
1006        Node end = (Node)ann.getEndNode();
1007        String type = ann.getType();
1008
1009        //DB stuff
1010        Long annGlobalID = null;
1011        stmt.setLong(1,lrID.longValue());
1012        stmt.setLong(2,ann.getId().longValue());
1013        stmt.setLong(3,asetID.longValue());
1014        stmt.setLong(4,start.getId().longValue());
1015        stmt.setLong(5,start.getOffset().longValue());
1016        stmt.setLong(6,end.getId().longValue());
1017        stmt.setLong(7,end.getOffset().longValue());
1018        stmt.setString(8,type);
1019        stmt.registerOutParameter(9,java.sql.Types.BIGINT);
1020
1021        stmt.execute();
1022
1023        annGlobalID = new Long(stmt.getLong(9));
1024
1025        //2.1. set annotation features
1026        FeatureMap features = ann.getFeatures();
1027        Assert.assertNotNull(features);
1028//        createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1029        createFeaturesBulk(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1030      } //while
1031    }//try
1032    catch(SQLException sqle) {
1033
1034      switch(sqle.getErrorCode()) {
1035
1036        case DBHelper.X_ORACLE_INVALID_ANNOTATION_TYPE:
1037          throw new PersistenceException(
1038                              "can't create annotation in DB, [invalid annotation type]");
1039        default:
1040          throw new PersistenceException(
1041                "can't create annotation in DB: ["+ sqle.getMessage()+"]");
1042      }//switch
1043    }//catch
1044    finally {
1045      DBHelper.cleanup(stmt);
1046    }
1047  }//func
1048
1049
1050
1051  /** creates a LR of type Corpus  */
1052  private Corpus createCorpus(Corpus corp,SecurityInfo secInfo, boolean newTransPerDocument)
1053    throws PersistenceException,SecurityException {
1054
1055    //1. create an LR entry for the corpus (T_LANG_RESOURCE table)
1056    Long lrID = createLR(DBHelper.CORPUS_CLASS,corp.getName(),secInfo,null);
1057
1058    //2.create am entry in the T_COPRUS table
1059    Long corpusID = null;
1060    //DB stuff
1061    CallableStatement stmt = null;
1062    try {
1063      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.create_corpus(?,?) }");
1064      stmt.setLong(1,lrID.longValue());
1065      stmt.registerOutParameter(2,java.sql.Types.BIGINT);
1066      stmt.execute();
1067      corpusID = new Long(stmt.getLong(2));
1068    }
1069    catch(SQLException sqle) {
1070      throw new PersistenceException("can't create corpus [step 2] in DB: ["+ sqle.getMessage()+"]");
1071    }
1072    finally {
1073      DBHelper.cleanup(stmt);
1074    }
1075
1076    //3. for each document in the corpus call createDocument()
1077    Iterator itDocuments = corp.iterator();
1078    Vector dbDocs = new Vector();
1079    while (itDocuments.hasNext()) {
1080      Document doc = (Document)itDocuments.next();
1081
1082      //3.1. ensure that the document is either transient or is from the ...
1083      // same DataStore
1084      if (doc.getLRPersistenceId() == null) {
1085        //transient document
1086
1087        //now this is a bit ugly patch, the transaction related functionality
1088        //should not be in this method
1089        if (newTransPerDocument) {
1090          beginTrans();
1091        }
1092
1093        Document dbDoc = createDocument(doc,corpusID,secInfo);
1094
1095        if (newTransPerDocument) {
1096          commitTrans();
1097        }
1098
1099        dbDocs.add(dbDoc);
1100        //8. let the world know
1101        fireResourceAdopted(new DatastoreEvent(this,
1102                                                DatastoreEvent.RESOURCE_ADOPTED,
1103                                                dbDoc,
1104                                                dbDoc.getLRPersistenceId()
1105                                              )
1106                            );
1107
1108        //9. fire also resource written event because it's now saved
1109        fireResourceWritten(new DatastoreEvent(this,
1110                                                DatastoreEvent.RESOURCE_WRITTEN,
1111                                                dbDoc,
1112                                                dbDoc.getLRPersistenceId()
1113                                              )
1114                           );
1115
1116      }
1117      else if (doc.getDataStore().equals(this)) {
1118        //persistent doc from the same DataStore
1119        fireResourceAdopted(
1120            new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
1121                               doc,
1122                               doc.getLRPersistenceId()));
1123
1124        //6. fire also resource written event because it's now saved
1125        fireResourceWritten(
1126          new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
1127                              doc,
1128                              doc.getLRPersistenceId()));
1129      }
1130      else {
1131        //persistent doc from other datastore
1132        //skip
1133        gate.util.Err.prln("document ["+doc.getLRPersistenceId()+"] is adopted from another "+
1134                            " datastore. Skipped.");
1135      }
1136    }
1137
1138    //4. create features
1139//    createFeatures(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1140    createFeaturesBulk(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1141
1142    //5. create a DatabaseCorpusImpl and return it
1143/*    Corpus dbCorpus = new DatabaseCorpusImpl(corp.getName(),
1144                                             this,
1145                                              lrID,
1146                                              corp.getFeatures(),
1147                                              dbDocs);
1148*/
1149
1150    Corpus dbCorpus = null;
1151    FeatureMap params = Factory.newFeatureMap();
1152    HashMap initData = new HashMap();
1153
1154    initData.put("DS",this);
1155    initData.put("LR_ID",lrID);
1156    initData.put("CORP_NAME",corp.getName());
1157    initData.put("CORP_FEATURES",corp.getFeatures());
1158    initData.put("CORP_SUPPORT_LIST",dbDocs);
1159
1160    params.put("initData__$$__", initData);
1161
1162    try {
1163      //here we create the persistent LR via Factory, so it's registered
1164      //in GATE
1165      dbCorpus = (Corpus)Factory.createResource("gate.corpora.DatabaseCorpusImpl", params);
1166    }
1167    catch (gate.creole.ResourceInstantiationException ex) {
1168      throw new GateRuntimeException(ex.getMessage());
1169    }
1170
1171    //6. done
1172    return dbCorpus;
1173  }
1174
1175
1176
1177  /**
1178   * Get a resource from the persistent store.
1179   * <B>Don't use this method - use Factory.createResource with
1180   * DataStore and DataStoreInstanceId parameters set instead.</B>
1181   */
1182  public LanguageResource getLr(String lrClassName, Object lrPersistenceId)
1183  throws PersistenceException,SecurityException {
1184
1185    LanguageResource result = null;
1186
1187    //0. preconditions
1188    Assert.assertNotNull(lrPersistenceId);
1189
1190    //1. check session
1191    if (null == this.session) {
1192      throw new SecurityException("session not set");
1193    }
1194
1195    if (false == this.ac.isValidSession(this.session)) {
1196      throw new SecurityException("invalid session supplied");
1197    }
1198
1199    //2. check permissions
1200    if (false == canReadLR(lrPersistenceId)) {
1201      throw new SecurityException("insufficient privileges");
1202    }
1203
1204    //3. get resource from DB
1205    if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
1206      result = readDocument(lrPersistenceId);
1207      Assert.assertTrue(result instanceof DatabaseDocumentImpl);
1208    }
1209    else if (lrClassName.equals(DBHelper.CORPUS_CLASS)) {
1210      result = readCorpus(lrPersistenceId);
1211      Assert.assertTrue(result instanceof DatabaseCorpusImpl);
1212    }
1213    else {
1214      throw new IllegalArgumentException("resource class should be either Document or Corpus");
1215    }
1216
1217    //4. postconditions
1218    Assert.assertNotNull(result.getDataStore());
1219    Assert.assertTrue(result.getDataStore() instanceof DatabaseDataStore);
1220    Assert.assertNotNull(result.getLRPersistenceId());
1221
1222    //5. register the read doc as listener for sync events
1223    addDatastoreListener((DatastoreListener)result);
1224
1225    //6. add the resource to the list of dependent resources - i.e. the ones that the
1226    //data store should take care upon closing [and call sync()]
1227    this.dependentResources.add(result);
1228
1229    //7. done
1230    return result;
1231  }
1232
1233
1234
1235  /** Get a list of the types of LR that are present in the data store. */
1236  public List getLrTypes() throws PersistenceException {
1237
1238    Vector lrTypes = new Vector();
1239    Statement stmt = null;
1240    ResultSet rs = null;
1241
1242    try {
1243      stmt = this.jdbcConn.createStatement();
1244      rs = stmt.executeQuery(" SELECT lrtp_type " +
1245                             " FROM   "+Gate.DB_OWNER+".t_lr_type LRTYPE ");
1246
1247      while (rs.next()) {
1248        //access by index is faster
1249        String lrType = rs.getString(1);
1250        lrTypes.add(lrType);
1251      }
1252
1253      return lrTypes;
1254    }
1255    catch(SQLException sqle) {
1256      throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
1257    }
1258    finally {
1259      DBHelper.cleanup(rs);
1260      DBHelper.cleanup(stmt);
1261    }
1262  }
1263
1264
1265
1266  /** Get a list of the IDs of LRs of a particular type that are present. */
1267  public List getLrIds(String lrType) throws PersistenceException {
1268
1269    Vector lrIDs = new Vector();
1270    PreparedStatement stmt = null;
1271    ResultSet rs = null;
1272
1273    try {
1274      stmt = this.jdbcConn.prepareStatement(
1275                      " SELECT lr_id " +
1276                      " FROM   "+Gate.DB_OWNER+".t_lang_resource LR, " +
1277                      "        "+Gate.DB_OWNER+".t_lr_type LRTYPE " +
1278                      " WHERE  LR.lr_type_id = LRTYPE.lrtp_id " +
1279                      "        AND LRTYPE.lrtp_type = ? " +
1280                      " ORDER BY lr_name"
1281                      );
1282      stmt.setString(1,lrType);
1283      ((OraclePreparedStatement)stmt).setRowPrefetch(DBHelper.CHINK_SIZE_SMALL);
1284      stmt.execute();
1285      rs = stmt.getResultSet();
1286
1287      while (rs.next()) {
1288        //access by index is faster
1289        Long lrID = new Long(rs.getLong(1));
1290        lrIDs.add(lrID);
1291      }
1292
1293      return lrIDs;
1294    }
1295    catch(SQLException sqle) {
1296      throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
1297    }
1298    finally {
1299      DBHelper.cleanup(rs);
1300      DBHelper.cleanup(stmt);
1301    }
1302
1303  }
1304
1305
1306
1307  /** Get a list of the names of LRs of a particular type that are present. */
1308  public List getLrNames(String lrType) throws PersistenceException {
1309
1310    Vector lrNames = new Vector();
1311    PreparedStatement stmt = null;
1312    ResultSet rs = null;
1313
1314    try {
1315      stmt = this.jdbcConn.prepareStatement(
1316                " SELECT lr_name " +
1317                " FROM   "+Gate.DB_OWNER+".t_lang_resource LR, " +
1318                "        t_lr_type LRTYPE " +
1319                " WHERE  LR.lr_type_id = LRTYPE.lrtp_id " +
1320                "        AND LRTYPE.lrtp_type = ? " +
1321                " ORDER BY lr_name desc"
1322                );
1323      stmt.setString(1,lrType);
1324      ((OraclePreparedStatement)stmt).setRowPrefetch(DBHelper.CHINK_SIZE_SMALL);
1325      stmt.execute();
1326      rs = stmt.getResultSet();
1327
1328      while (rs.next()) {
1329        //access by index is faster
1330        String lrName = rs.getString(1);
1331        lrNames.add(lrName);
1332      }
1333
1334      return lrNames;
1335    }
1336    catch(SQLException sqle) {
1337      throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
1338    }
1339    finally {
1340      DBHelper.cleanup(rs);
1341      DBHelper.cleanup(stmt);
1342    }
1343  }
1344
1345
1346
1347  /** Gets a timestamp marker that will be used for all changes made in
1348   *  the database so that subsequent calls to deleteSince() could restore (partly)
1349   *  the database state as it was before the update. <B>NOTE:</B> Restoring the previous
1350   *  state may not be possible at all (i.e. if DELETE is performed)
1351   *   */
1352  public Long timestamp()
1353    throws PersistenceException{
1354
1355    CallableStatement stmt = null;
1356
1357    try {
1358      stmt = this.jdbcConn.prepareCall(
1359                "{ call "+Gate.DB_OWNER+".persist.get_timestamp(?)} ");
1360      //numbers generated from Oracle sequences are BIGINT
1361      stmt.registerOutParameter(1,java.sql.Types.BIGINT);
1362      stmt.execute();
1363      long result = stmt.getLong(1);
1364
1365      return new Long(result);
1366    }
1367    catch(SQLException sqle) {
1368      throw new PersistenceException("can't get a timestamp from DB: ["+ sqle.getMessage()+"]");
1369    }
1370    finally {
1371      DBHelper.cleanup(stmt);
1372    }
1373  }
1374
1375
1376
1377  /**
1378   * Checks if the user (identified by the sessionID)
1379   *  has read access to the LR
1380   */
1381  public boolean canReadLR(Object lrID)
1382    throws PersistenceException, SecurityException{
1383
1384    return canAccessLR((Long) lrID,READ_ACCESS);
1385  }
1386
1387
1388
1389  /**
1390   * Checks if the user (identified by the sessionID)
1391   * has write access to the LR
1392   */
1393  public boolean canWriteLR(Object lrID)
1394    throws PersistenceException, SecurityException{
1395
1396    return canAccessLR((Long) lrID,WRITE_ACCESS);
1397  }
1398
1399
1400
1401  /**
1402   * Checks if the user (identified by the sessionID)
1403   * has some access (read/write) to the LR
1404   */
1405  private boolean canAccessLR(Long lrID,int mode)
1406    throws PersistenceException, SecurityException{
1407
1408    //0. preconditions
1409    Assert.assertTrue(READ_ACCESS == mode || WRITE_ACCESS == mode);
1410
1411    //1. is session initialised?
1412    if (null == this.session) {
1413      throw new SecurityException("user session not set");
1414    }
1415
1416    //2.first check the session and then check whether the user is member of the group
1417    if (this.ac.isValidSession(this.session) == false) {
1418      throw new SecurityException("invalid session supplied");
1419    }
1420
1421    CallableStatement stmt = null;
1422
1423    try {
1424      stmt = this.jdbcConn.prepareCall(
1425                "{ call "+Gate.DB_OWNER+".security.has_access_to_lr(?,?,?,?,?)} ");
1426      stmt.setLong(1,lrID.longValue());
1427      stmt.setLong(2,this.session.getUser().getID().longValue());
1428      stmt.setLong(3,this.session.getGroup().getID().longValue());
1429      stmt.setLong(4,mode);
1430
1431      stmt.registerOutParameter(5,java.sql.Types.NUMERIC);
1432      stmt.execute();
1433      int result = stmt.getInt(5);
1434
1435      return (ORACLE_TRUE == result);
1436    }
1437    catch(SQLException sqle) {
1438      throw new PersistenceException("can't check permissions in DB: ["+ sqle.getMessage()+"]");
1439    }
1440    finally {
1441      DBHelper.cleanup(stmt);
1442    }
1443  }
1444
1445
1446
1447  /** reads the content of a CLOB into the specified StringBuffer */
1448  public static void readCLOB(java.sql.Clob src, StringBuffer dest)
1449    throws SQLException, IOException {
1450
1451    int readLength = 0;
1452
1453    //1. empty the dest buffer
1454    dest.delete(0,dest.length());
1455
1456    //2. get Oracle CLOB
1457    CLOB clo = (CLOB)src;
1458
1459    //3. create temp buffer
1460    int buffSize = Math.max(INTERNAL_BUFFER_SIZE,clo.getBufferSize());
1461    char[] readBuffer = new char[buffSize];
1462
1463    //3. get Unicode stream
1464    Reader input = clo.getCharacterStream();
1465
1466    //4. read
1467    BufferedReader buffInput = new BufferedReader(input,INTERNAL_BUFFER_SIZE);
1468
1469    while ((readLength = buffInput.read(readBuffer, 0, INTERNAL_BUFFER_SIZE)) != -1) {
1470      dest.append(readBuffer, 0, readLength);
1471    }
1472
1473    //5.close streams
1474    buffInput.close();
1475    input.close();
1476
1477  }
1478
1479
1480
1481  /** writes the content of a String into the specified CLOB object */
1482  public static void writeCLOB(String src,java.sql.Clob dest)
1483    throws SQLException, IOException {
1484
1485    //preconditions
1486    Assert.assertNotNull(src);
1487
1488    //1. get Oracle CLOB
1489    CLOB clo = (CLOB)dest;
1490
1491    //2. get Unicode stream
1492    Writer output = clo.getCharacterOutputStream();
1493
1494    //3. write
1495    BufferedWriter buffOutput = new BufferedWriter(output,INTERNAL_BUFFER_SIZE);
1496    buffOutput.write(src.toString());
1497
1498    //4. flushing is a good idea [although BufferedWriter::close() calls it this is
1499    //implementation specific]
1500    buffOutput.flush();
1501    output.flush();
1502
1503    //5.close streams
1504    buffOutput.close();
1505    output.close();
1506  }
1507
1508
1509
1510  /** writes the content of a StringBuffer into the specified CLOB object */
1511  public static void writeCLOB(StringBuffer src,java.sql.Clob dest)
1512    throws SQLException, IOException {
1513
1514    //delegate
1515    writeCLOB(src.toString(),dest);
1516  }
1517
1518
1519
1520  /**
1521   *  reads the content of the specified BLOB object and returns the object
1522   *  contained.
1523   *  NOTE: the BLOB is expected to contain serializable objects, not just any
1524   *  binary stream
1525   */
1526  public static Object readBLOB(java.sql.Blob src)
1527    throws SQLException, IOException,ClassNotFoundException {
1528
1529    int readLength = 0;
1530    Object result = null;
1531
1532    //0. preconditions
1533    Assert.assertNotNull(src);
1534
1535    //2. get Oracle BLOB
1536    BLOB blo = (BLOB)src;
1537
1538    //3. get binary stream
1539    InputStream input = blo.getBinaryStream();
1540    Assert.assertNotNull(input);
1541
1542    //4. read
1543    ObjectInputStream ois = new ObjectInputStream(input);
1544    result = ois.readObject();
1545
1546    //5.close streams
1547    ois.close();
1548    input.close();
1549
1550    return result;
1551  }
1552
1553
1554
1555  /**
1556   *  writes the specified object into the BLOB
1557   *  NOTE: the object should be serializable
1558   */
1559  public static void writeBLOB(Object src,java.sql.Blob dest)
1560    throws SQLException, IOException {
1561
1562    //preconditions
1563    Assert.assertNotNull(src);
1564
1565    //1. get Oracle CLOB
1566    BLOB blo = (BLOB)dest;
1567
1568    //2. get Unicode stream
1569    OutputStream output = blo.getBinaryOutputStream();
1570
1571    //3. write
1572    ObjectOutputStream oos = new ObjectOutputStream(output);
1573    oos.writeObject(src);
1574
1575    //4. flushing is a good idea
1576    //[although ::close() calls it this is implementation specific]
1577    oos.flush();
1578    output.flush();
1579
1580    //5.close streams
1581    oos.close();
1582    output.close();
1583  }
1584
1585
1586
1587  /**
1588   *  creates a feature of the specified type/value/valueType/key for the specified entity
1589   *  Entity is one of: LR, Annotation
1590   *  Value types are: boolean, int, long, string, float, Object
1591   */
1592  private Long _createFeature(Long entityID,
1593                              int entityType,
1594                              String key,
1595                              Object value,
1596                              int valueType,
1597                              CallableStatement stmt)
1598    throws PersistenceException {
1599
1600    //1. store in DB
1601    Long featID = null;
1602//    CallableStatement stmt = null;
1603
1604    try {
1605//      stmt = this.jdbcConn.prepareCall(
1606//                "{ call "+Gate.DB_OWNER+".persist.create_feature(?,?,?,?,?,?,?)} ");
1607
1608      //1.1 set known values + NULLs
1609      stmt.setLong(1,entityID.longValue());
1610      stmt.setLong(2,entityType);
1611      stmt.setString(3,key);
1612      stmt.setNull(4,java.sql.Types.NUMERIC);
1613      stmt.setNull(5,java.sql.Types.VARCHAR);
1614      stmt.setLong(6,valueType);
1615      stmt.registerOutParameter(7,java.sql.Types.BIGINT);
1616
1617      //1.2 set proper data
1618      switch(valueType) {
1619
1620        case DBHelper.VALUE_TYPE_NULL:
1621          break;
1622
1623        case DBHelper.VALUE_TYPE_BOOLEAN:
1624
1625          boolean b = ((Boolean)value).booleanValue();
1626          stmt.setLong(4, b ? this.ORACLE_TRUE : this.ORACLE_FALSE);
1627          break;
1628
1629        case DBHelper.VALUE_TYPE_INTEGER:
1630
1631          stmt.setLong(4,((Integer)value).intValue());
1632          break;
1633
1634        case DBHelper.VALUE_TYPE_LONG:
1635
1636          stmt.setLong(4,((Long)value).longValue());
1637          break;
1638
1639        case DBHelper.VALUE_TYPE_FLOAT:
1640
1641          Double d = (Double)value;
1642          stmt.setDouble(4,d.doubleValue());
1643          break;
1644
1645        case DBHelper.VALUE_TYPE_BINARY:
1646          //ignore
1647          //will be handled later in processing
1648          break;
1649
1650        case DBHelper.VALUE_TYPE_STRING:
1651
1652          String s = (String)value;
1653          //does it fin into a varchar2?
1654          if (fitsInVarchar2(s)) {
1655            stmt.setString(5,s);
1656          }
1657          break;
1658
1659        default:
1660          throw new IllegalArgumentException("unsuppoeted feature type");
1661      }
1662
1663      stmt.execute();
1664      featID = new Long(stmt.getLong(7));
1665    }
1666    catch(SQLException sqle) {
1667
1668      switch(sqle.getErrorCode()) {
1669        case DBHelper.X_ORACLE_INVALID_FEATURE_TYPE:
1670          throw new PersistenceException("can't create feature [step 1],"+
1671                      "[invalid feature type] in DB: ["+ sqle.getMessage()+"]");
1672        default:
1673          throw new PersistenceException("can't create feature [step 1] in DB: ["+
1674                                                      sqle.getMessage()+"]");
1675      }
1676    }
1677    finally {
1678//      DBHelper.cleanup(stmt);
1679    }
1680
1681    return featID;
1682  }
1683
1684
1685  /**
1686   *  creates a feature of the specified type/value/valueType/key for the specified entity
1687   *  Entity is one of: LR, Annotation
1688   *  Value types are: boolean, int, long, string, float, Object
1689   */
1690  private void _createFeatureBulk(Vector features,
1691                                  CallableStatement stmt,
1692                                  ArrayDescriptor adNumber,
1693                                  ArrayDescriptor adString)
1694    throws PersistenceException {
1695
1696    String[] stringValues = new String[VARRAY_SIZE];
1697    long[] numberValues = new long[VARRAY_SIZE];
1698    double[] floatValues = new double[VARRAY_SIZE];
1699    long[] entityIDs = new long[VARRAY_SIZE];
1700    long[] entityTypes = new long[VARRAY_SIZE];
1701    String[] keys = new String[VARRAY_SIZE];
1702    long[] valueTypes = new long[VARRAY_SIZE];
1703
1704//System.out.println("num features=["+features.size()+"]");
1705    //1. store in DB
1706    try {
1707
1708      int ftInd = 0;
1709      int arrInd = 0;
1710      Iterator it = features.iterator();
1711
1712      while (it.hasNext()) {
1713
1714        Feature currFeature = (Feature)it.next();
1715        entityIDs[arrInd] = currFeature.entityID.longValue();
1716        entityTypes[arrInd] = currFeature.entityType;
1717        keys[arrInd] = currFeature.key;
1718        valueTypes[arrInd] = currFeature.valueType;
1719//System.out.println("ftype=["+currFeature.valueType+"]");
1720        //preconditions
1721        Assert.assertTrue(currFeature.valueType == DBHelper.VALUE_TYPE_BOOLEAN ||
1722                          currFeature.valueType == DBHelper.VALUE_TYPE_FLOAT ||
1723                          currFeature.valueType == DBHelper.VALUE_TYPE_INTEGER ||
1724                          currFeature.valueType == DBHelper.VALUE_TYPE_LONG ||
1725                          currFeature.valueType == DBHelper.VALUE_TYPE_NULL ||
1726                          currFeature.valueType == DBHelper.VALUE_TYPE_STRING
1727                          );
1728
1729
1730        Object value = currFeature.value;
1731
1732        switch(currFeature.valueType) {
1733
1734          case DBHelper.VALUE_TYPE_NULL:
1735            numberValues[arrInd] = 0;
1736            floatValues[arrInd] = 0;
1737            stringValues[arrInd] = "";
1738            break;
1739
1740          case DBHelper.VALUE_TYPE_BOOLEAN:
1741            boolean b = ((Boolean)value).booleanValue();
1742            numberValues[arrInd] = b ? this.ORACLE_TRUE : this.ORACLE_FALSE;
1743            floatValues[arrInd] = 0;
1744            stringValues[arrInd] = "";
1745            break;
1746
1747          case DBHelper.VALUE_TYPE_INTEGER:
1748            numberValues[arrInd] = ((Integer)value).intValue();
1749            floatValues[arrInd] = 0;
1750            stringValues[arrInd] = "";
1751            break;
1752
1753          case DBHelper.VALUE_TYPE_LONG:
1754            numberValues[arrInd] = ((Long)value).longValue();
1755            floatValues[arrInd] = 0;
1756            stringValues[arrInd] = "";
1757            break;
1758
1759          case DBHelper.VALUE_TYPE_FLOAT:
1760            floatValues[arrInd] = ((Double)value).doubleValue();
1761            numberValues[arrInd] = 0;
1762            stringValues[arrInd] = "";
1763            break;
1764
1765          case DBHelper.VALUE_TYPE_BINARY:
1766            Assert.fail();
1767            break;
1768
1769          case DBHelper.VALUE_TYPE_STRING:
1770            String s = (String)value;
1771            //does it fin into a varchar2?
1772
1773            if (fitsInVarchar2(s)) {
1774              stringValues[arrInd] = s;
1775              floatValues[arrInd] = 0;
1776              numberValues[arrInd] = 0;
1777            }
1778            else {
1779              Assert.fail();
1780            }
1781            break;
1782
1783          default:
1784            throw new IllegalArgumentException("unsuppoeted feature type");
1785        }
1786
1787        //save the features?
1788        ftInd++;
1789        arrInd++;
1790
1791        if (ftInd == features.size() || arrInd == VARRAY_SIZE) {
1792
1793          if (arrInd == VARRAY_SIZE) {
1794            arrInd = 0;
1795          }
1796//System.out.println("1");
1797          ARRAY arrEntityIDs = new ARRAY(adNumber, this.jdbcConn,entityIDs);
1798          ARRAY arrEntityTypes = new ARRAY(adNumber, this.jdbcConn,entityTypes);
1799          ARRAY arrKeys = new ARRAY(adString, this.jdbcConn,keys);
1800          ARRAY arrValueTypes = new ARRAY(adNumber, this.jdbcConn,valueTypes);
1801          ARRAY arrNumberValues = new ARRAY(adNumber, this.jdbcConn,numberValues);
1802          ARRAY arrFloatValues = new ARRAY(adNumber, this.jdbcConn,floatValues);
1803          ARRAY arrStringValues = new ARRAY(adString, this.jdbcConn,stringValues);
1804
1805          OracleCallableStatement ostmt = (OracleCallableStatement)stmt;
1806          ostmt.setARRAY(1,arrEntityIDs);
1807          ostmt.setARRAY(2,arrEntityTypes);
1808          ostmt.setARRAY(3,arrKeys);
1809          ostmt.setARRAY(4,arrNumberValues);
1810          ostmt.setARRAY(5,arrFloatValues);
1811          ostmt.setARRAY(6,arrStringValues);
1812          ostmt.setARRAY(7,arrValueTypes);
1813          ostmt.setInt(8, arrInd == 0 ? VARRAY_SIZE : arrInd);
1814
1815          ostmt.execute();
1816        }
1817      }
1818    }
1819    catch(SQLException sqle) {
1820
1821      switch(sqle.getErrorCode()) {
1822
1823        case DBHelper.X_ORACLE_INVALID_FEATURE_TYPE:
1824          throw new PersistenceException("can't create feature [step 1],"+
1825                      "[invalid feature type] in DB: ["+ sqle.getMessage()+"]");
1826        default:
1827          throw new PersistenceException("can't create feature [step 1] in DB: ["+
1828                                                      sqle.getMessage()+"]");
1829      }
1830    }
1831  }
1832
1833  /**
1834   *  updates the value of a feature where the value is string (>4000 bytes, stored as CLOB)
1835   *  or Object (stored as BLOB)
1836   */
1837  private void _updateFeatureLOB(Long featID,Object value, int valueType)
1838    throws PersistenceException {
1839
1840    //NOTE: at this point value is never an array,
1841    // although the type may claim so
1842
1843    //0. preconditions
1844    Assert.assertTrue(valueType == DBHelper.VALUE_TYPE_BINARY ||
1845                  valueType == DBHelper.VALUE_TYPE_STRING);
1846
1847
1848    //1. get the row to be updated
1849    PreparedStatement stmtA = null;
1850    ResultSet rsA = null;
1851    Clob clobValue = null;
1852    Blob blobValue = null;
1853
1854    try {
1855      String sql = " select ft_long_character_value, " +
1856                   "        ft_binary_value " +
1857                   " from  "+Gate.DB_OWNER+".t_feature " +
1858                   " where  ft_id = ? ";
1859
1860      stmtA = this.jdbcConn.prepareStatement(sql);
1861      stmtA.setLong(1,featID.longValue());
1862      stmtA.execute();
1863      rsA = stmtA.getResultSet();
1864
1865      if (false == rsA.next()) {
1866        throw new PersistenceException("Incorrect feature ID supplied ["+featID+"]");
1867      }
1868
1869      //NOTE1: if the result set contains LOBs always read them
1870      // in the order they appear in the SQL query
1871      // otherwise data will be lost
1872      //NOTE2: access by index rather than name is usually faster
1873      clobValue = rsA.getClob(1);
1874      blobValue = rsA.getBlob(2);
1875
1876      //blob or clob?
1877      if (valueType == DBHelper.VALUE_TYPE_BINARY) {
1878        //blob
1879        writeBLOB(value,blobValue);
1880      }
1881      else if (valueType == DBHelper.VALUE_TYPE_STRING) {
1882        //clob
1883        String s = (String)value;
1884        writeCLOB(s,clobValue);
1885      }
1886      else {
1887        Assert.fail();
1888      }
1889    }
1890    catch(SQLException sqle) {
1891      throw new PersistenceException("can't create feature [step 2] in DB: ["+ sqle.getMessage()+"]");
1892    }
1893    catch(IOException ioe) {
1894      throw new PersistenceException("can't create feature [step 2] in DB: ["+ ioe.getMessage()+"]");
1895    }
1896    finally {
1897      DBHelper.cleanup(rsA);
1898      DBHelper.cleanup(stmtA);
1899    }
1900
1901  }
1902
1903
1904
1905  /**
1906   *  creates a feature with the specified type/key/value for the specified entity
1907   *  entitties are either LRs ot Annotations
1908   *  valid values are: boolean,
1909   *                    int,
1910   *                    long,
1911   *                    string,
1912   *                    float,
1913   *                    Object,
1914   *                    boolean List,
1915   *                    int List,
1916   *                    long List,
1917   *                    string List,
1918   *                    float List,
1919   *                    Object List
1920   *
1921   */
1922  private void createFeature(Long entityID, int entityType,String key, Object value, CallableStatement stmt)
1923    throws PersistenceException {
1924
1925    //1. what kind of feature value is this?
1926//System.out.println("key=["+key+"], val=["+value+"]");
1927    int valueType = findFeatureType(value);
1928
1929    //2. how many elements do we store?
1930    Vector elementsToStore = new Vector();
1931
1932    switch(valueType) {
1933      case DBHelper.VALUE_TYPE_NULL:
1934      case DBHelper.VALUE_TYPE_BINARY:
1935      case DBHelper.VALUE_TYPE_BOOLEAN:
1936      case DBHelper.VALUE_TYPE_FLOAT:
1937      case DBHelper.VALUE_TYPE_INTEGER:
1938      case DBHelper.VALUE_TYPE_LONG:
1939      case DBHelper.VALUE_TYPE_STRING:
1940        elementsToStore.add(value);
1941        break;
1942
1943      default:
1944        //arrays
1945        List arr = (List)value;
1946        Iterator itValues = arr.iterator();
1947
1948        while (itValues.hasNext()) {
1949          elementsToStore.add(itValues.next());
1950        }
1951
1952        //normalize , i.e. ignore arrays
1953        if (valueType == DBHelper.VALUE_TYPE_BINARY_ARR)
1954          valueType = DBHelper.VALUE_TYPE_BINARY;
1955        else if (valueType == DBHelper.VALUE_TYPE_BOOLEAN_ARR)
1956          valueType = DBHelper.VALUE_TYPE_BOOLEAN;
1957        else if (valueType == DBHelper.VALUE_TYPE_FLOAT_ARR)
1958          valueType = DBHelper.VALUE_TYPE_FLOAT;
1959        else if (valueType == DBHelper.VALUE_TYPE_INTEGER_ARR)
1960          valueType = DBHelper.VALUE_TYPE_INTEGER;
1961        else if (valueType == DBHelper.VALUE_TYPE_LONG_ARR)
1962          valueType = DBHelper.VALUE_TYPE_LONG;
1963        else if (valueType == DBHelper.VALUE_TYPE_STRING_ARR)
1964          valueType = DBHelper.VALUE_TYPE_STRING;
1965    }
1966
1967    //3. for all elements:
1968    for (int i=0; i< elementsToStore.size(); i++) {
1969
1970        Object currValue = elementsToStore.elementAt(i);
1971
1972        //3.1. create a dummy feature [LOB hack]
1973        Long featID = _createFeature(entityID,entityType,key,currValue,valueType,stmt);
1974
1975        //3.2. update CLOBs if needed
1976        if (valueType == DBHelper.VALUE_TYPE_STRING) {
1977          //does this string fit into a varchar2 or into clob?
1978          String s = (String)currValue;
1979          if (false == this.fitsInVarchar2(s)) {
1980            // Houston, we have a problem
1981            // put the string into a clob
1982            _updateFeatureLOB(featID,value,valueType);
1983          }
1984        }
1985        else if (valueType == DBHelper.VALUE_TYPE_BINARY) {
1986          //3.3. BLOBs
1987            _updateFeatureLOB(featID,value,valueType);
1988        }
1989    }
1990
1991
1992  }
1993
1994
1995  /**
1996   *  splits complex features (Lists) into a vector of Feature entries
1997   *  each entry contains the entity id,
1998   *                          entity type,
1999   *                          feature key
2000   *                          feature value
2001   *                          value type
2002   *
2003   */
2004  private Vector normalizeFeature(Long entityID, int entityType,String key, Object value)
2005    throws PersistenceException {
2006
2007    //1. what kind of feature value is this?
2008    int valueType = findFeatureType(value);
2009
2010    //2. how many elements do we store?
2011    Vector elementsToStore = new Vector();
2012    Vector features = new Vector();
2013
2014    switch(valueType) {
2015      case DBHelper.VALUE_TYPE_NULL:
2016      case DBHelper.VALUE_TYPE_BINARY:
2017      case DBHelper.VALUE_TYPE_BOOLEAN:
2018      case DBHelper.VALUE_TYPE_FLOAT:
2019      case DBHelper.VALUE_TYPE_INTEGER:
2020      case DBHelper.VALUE_TYPE_LONG:
2021      case DBHelper.VALUE_TYPE_STRING:
2022        elementsToStore.add(value);
2023        break;
2024
2025      default:
2026        //arrays
2027        List arr = (List)value;
2028        Iterator itValues = arr.iterator();
2029
2030        while (itValues.hasNext()) {
2031          elementsToStore.add(itValues.next());
2032        }
2033
2034        //normalize , i.e. ignore arrays
2035        if (valueType == DBHelper.VALUE_TYPE_BINARY_ARR)
2036          valueType = DBHelper.VALUE_TYPE_BINARY;
2037        else if (valueType == DBHelper.VALUE_TYPE_BOOLEAN_ARR)
2038          valueType = DBHelper.VALUE_TYPE_BOOLEAN;
2039        else if (valueType == DBHelper.VALUE_TYPE_FLOAT_ARR)
2040          valueType = DBHelper.VALUE_TYPE_FLOAT;
2041        else if (valueType == DBHelper.VALUE_TYPE_INTEGER_ARR)
2042          valueType = DBHelper.VALUE_TYPE_INTEGER;
2043        else if (valueType == DBHelper.VALUE_TYPE_LONG_ARR)
2044          valueType = DBHelper.VALUE_TYPE_LONG;
2045        else if (valueType == DBHelper.VALUE_TYPE_STRING_ARR)
2046          valueType = DBHelper.VALUE_TYPE_STRING;
2047    }
2048
2049    for (int i=0; i< elementsToStore.size(); i++) {
2050
2051      Object currValue = elementsToStore.elementAt(i);
2052      Feature currFeature = new Feature(entityID,entityType,key,currValue,valueType);
2053      features.add(currFeature);
2054    }
2055
2056    return features;
2057  }
2058
2059
2060  /**
2061   *  checks if a String should be stores as VARCHAR2 or CLOB
2062   *  because the VARCHAR2 in Oracle is limited to 4000 <b>bytes</b>, not all
2063   *  the strings fit there. If a String is too long then it is store in the
2064   *  database as CLOB.
2065   *  Note that in the worst case 3 bytes are needed to represent a single character
2066   *  in a database with UTF8 encoding, which limits the string length to 4000/3
2067   *  (ORACLE_VARCHAR_LIMIT_BYTES)
2068   *  @see ORACLE_VARCHAR_LIMIT_BYTES
2069   */
2070  private boolean fitsInVarchar2(String s) {
2071
2072    return s.getBytes().length < this.ORACLE_VARCHAR_LIMIT_BYTES;
2073  }
2074
2075
2076
2077  /**
2078   *  helper metod
2079   *  iterates a FeatureMap and creates all its features in the database
2080   */
2081  private void createFeatures(Long entityID, int entityType, FeatureMap features)
2082    throws PersistenceException {
2083
2084    //0. prepare statement ad use it for all features
2085    CallableStatement stmt = null;
2086    CallableStatement stmtBulk = null;
2087    ArrayDescriptor adNumber = null;
2088    ArrayDescriptor adString = null;
2089
2090    try {
2091      stmt = this.jdbcConn.prepareCall(
2092                    "{ call "+Gate.DB_OWNER+".persist.create_feature(?,?,?,?,?,?,?)} ");
2093
2094      stmtBulk = this.jdbcConn.prepareCall(
2095                    "{ call "+Gate.DB_OWNER+".persist.create_feature_bulk(?,?,?,?,?,?,?,?)} ");
2096
2097      adNumber = ArrayDescriptor.createDescriptor("GATEADMIN.PERSIST.INTARRAY", this.jdbcConn);
2098      adString = ArrayDescriptor.createDescriptor("GATEADMIN.PERSIST.CHARARRAY", this.jdbcConn);
2099    }
2100    catch (SQLException sqle) {
2101      throw new PersistenceException(sqle);
2102    }
2103
2104    /* when some day Java has macros, this will be a macro */
2105    Set entries = features.entrySet();
2106    Iterator itFeatures = entries.iterator();
2107    while (itFeatures.hasNext()) {
2108      Map.Entry entry = (Map.Entry)itFeatures.next();
2109      String key = (String)entry.getKey();
2110      Object value = entry.getValue();
2111      createFeature(entityID,entityType,key,value,stmt);
2112    }
2113
2114    //3. cleanup
2115    DBHelper.cleanup(stmt);
2116  }
2117
2118
2119  /**
2120   *  helper metod
2121   *  iterates a FeatureMap and creates all its features in the database
2122   *
2123   *  since it uses Oracle VARRAYs the roundtrips between the client and the server
2124   *  are minimized
2125   *
2126   *  make sure the two types STRING_ARRAY and INT_ARRAY have the same name in the
2127   *  PL/SQL files
2128   *
2129   *  also when referencing the types always use the schema owner in upper case
2130   *  because the jdbc driver is buggy (see MetaLink note if u care)
2131   */
2132  private void createFeaturesBulk(Long entityID, int entityType, FeatureMap features)
2133    throws PersistenceException {
2134
2135    //0. prepare statement ad use it for all features
2136    CallableStatement stmt = null;
2137    CallableStatement stmtBulk = null;
2138    ArrayDescriptor adNumber = null;
2139    ArrayDescriptor adString = null;
2140
2141    try {
2142      stmt = this.jdbcConn.prepareCall(
2143                    "{ call "+Gate.DB_OWNER+".persist.create_feature(?,?,?,?,?,?,?)} ");
2144
2145      stmtBulk = this.jdbcConn.prepareCall(
2146                    "{ call "+Gate.DB_OWNER+".persist.create_feature_bulk(?,?,?,?,?,?,?,?)} ");
2147
2148      //ACHTUNG!!!
2149      //using toUpper for schema owner is necessary because of the dull JDBC driver
2150      //otherwise u'll end up with "invalid name pattern" Oracle error
2151      adString = ArrayDescriptor.createDescriptor(Gate.DB_OWNER.toUpperCase()+".STRING_ARRAY", this.jdbcConn);
2152      adNumber = ArrayDescriptor.createDescriptor(Gate.DB_OWNER.toUpperCase()+".INT_ARRAY", this.jdbcConn);
2153    }
2154    catch (SQLException sqle) {
2155      throw new PersistenceException(sqle);
2156    }
2157
2158    /* when some day Java has macros, this will be a macro */
2159    Vector entityFeatures = new Vector();
2160
2161    Set entries = features.entrySet();
2162    Iterator itFeatures = entries.iterator();
2163    while (itFeatures.hasNext()) {
2164      Map.Entry entry = (Map.Entry)itFeatures.next();
2165      String key = (String)entry.getKey();
2166      Object value = entry.getValue();
2167      Vector normalizedFeatures = normalizeFeature(entityID,entityType,key,value);
2168      entityFeatures.addAll(normalizedFeatures);
2169    }
2170
2171    //iterate all features, store LOBs directly and other features with bulk store
2172    Iterator itEntityFeatures = entityFeatures.iterator();
2173
2174    while (itEntityFeatures.hasNext()) {
2175
2176      Feature currFeature = (Feature)itEntityFeatures.next();
2177
2178      if (currFeature.valueType == DBHelper.VALUE_TYPE_STRING) {
2179          //does this string fit into a varchar2 or into clob?
2180          String s = (String)currFeature.value;
2181          if (false == this.fitsInVarchar2(s)) {
2182            // Houston, we have a problem
2183            // put the string into a clob
2184            Long featID = _createFeature(currFeature.entityID,
2185                                         currFeature.entityType,
2186                                         currFeature.key,
2187                                         currFeature.value,
2188                                         currFeature.valueType,
2189                                         stmt);
2190            _updateFeatureLOB(featID,currFeature.value,currFeature.valueType);
2191            itEntityFeatures.remove();
2192          }
2193      }
2194      else if (currFeature.valueType == DBHelper.VALUE_TYPE_BINARY) {
2195        //3.3. BLOBs
2196        Long featID = _createFeature(currFeature.entityID,
2197                                     currFeature.entityType,
2198                                     currFeature.key,
2199                                     currFeature.value,
2200                                     currFeature.valueType,
2201                                     stmt);
2202        _updateFeatureLOB(featID,currFeature.value,currFeature.valueType);
2203        itEntityFeatures.remove();
2204      }
2205    }
2206
2207    //now we have the data for the bulk store
2208    _createFeatureBulk(entityFeatures, stmtBulk, adNumber, adString);
2209
2210    //3. cleanup
2211    DBHelper.cleanup(stmt);
2212    DBHelper.cleanup(stmtBulk);
2213  }
2214
2215
2216  /** get security information for LR . */
2217  public SecurityInfo getSecurityInfo(LanguageResource lr)
2218    throws PersistenceException {
2219
2220    //0. preconditions
2221    Assert.assertNotNull(lr);
2222    Assert.assertNotNull(lr.getLRPersistenceId());
2223    Assert.assertTrue(lr.getLRPersistenceId() instanceof Long);
2224    Assert.assertEquals(this,lr.getDataStore());
2225    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
2226                      lr instanceof DatabaseCorpusImpl);
2227
2228    PreparedStatement pstmt = null;
2229    ResultSet rs = null;
2230
2231    //1. read data
2232    Long userID = null;
2233    Long groupID = null;
2234    int  perm;
2235    try {
2236      String sql =  "   select lr_owner_user_id, "+
2237                    "          lr_owner_group_id, " +
2238                    "          lr_access_mode "+
2239                    "   from   "+gate.Gate.DB_OWNER+".t_lang_resource "+
2240                    "   where  lr_id = ?";
2241      pstmt = this.jdbcConn.prepareStatement(sql);
2242      pstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
2243      rs = pstmt.executeQuery();
2244
2245      if (false == rs.next()) {
2246        throw new PersistenceException("Invalid LR ID supplied - no data found");
2247      }
2248
2249      userID = new Long(rs.getLong("lr_owner_user_id"));
2250      groupID = new Long(rs.getLong("lr_owner_group_id"));
2251      perm = rs.getInt("lr_access_mode");
2252
2253      Assert.assertTrue(perm == SecurityInfo.ACCESS_GR_GW ||
2254                        perm == SecurityInfo.ACCESS_GR_OW ||
2255                        perm == SecurityInfo.ACCESS_OR_OW ||
2256                        perm == SecurityInfo.ACCESS_WR_GW);
2257    }
2258    catch(SQLException sqle) {
2259      throw new PersistenceException("Can't read document permissions from DB, error is [" +
2260                                      sqle.getMessage() +"]");
2261    }
2262    finally {
2263      DBHelper.cleanup(rs);
2264      DBHelper.cleanup(pstmt);
2265    }
2266
2267    //2. get data from AccessController
2268    User usr = null;
2269    Group grp = null;
2270    try {
2271      usr = this.ac.findUser(userID);
2272      grp = this.ac.findGroup(groupID);
2273    }
2274    catch (SecurityException se) {
2275      throw new PersistenceException("Invalid security settings found in DB [" +
2276                                      se.getMessage() +"]");
2277    }
2278
2279    //3. construct SecurityInfo
2280    SecurityInfo si = new SecurityInfo(perm,usr,grp);
2281
2282
2283    return si;
2284  }
2285
2286
2287
2288  /** set security information for LR . */
2289  public void setSecurityInfo(LanguageResource lr,SecurityInfo si)
2290    throws PersistenceException, SecurityException {
2291    throw new MethodNotImplementedException();
2292  }
2293
2294
2295
2296  /**
2297   *  helper method for getLR - reads LR of type Corpus
2298   */
2299  private DatabaseCorpusImpl readCorpus(Object lrPersistenceId)
2300    throws PersistenceException {
2301
2302    //0. preconditions
2303    Assert.assertNotNull(lrPersistenceId);
2304
2305    if (false == lrPersistenceId instanceof Long) {
2306      throw new IllegalArgumentException();
2307    }
2308
2309    //3. read from DB
2310    PreparedStatement pstmt = null;
2311    ResultSet rs = null;
2312    DatabaseCorpusImpl result = null;
2313
2314    try {
2315      String sql = " select lr_name " +
2316                   " from  "+Gate.DB_OWNER+".t_lang_resource " +
2317                   " where  lr_id = ? ";
2318      pstmt = this.jdbcConn.prepareStatement(sql);
2319      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2320      pstmt.execute();
2321      rs = pstmt.getResultSet();
2322
2323      if (false == rs.next()) {
2324        //ooops mo data found
2325        throw new PersistenceException("Invalid LR ID supplied - no data found");
2326      }
2327
2328      //4. fill data
2329
2330      //4.1 name
2331      String lrName = rs.getString("lr_name");
2332      Assert.assertNotNull(lrName);
2333
2334      //4.8 features
2335      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_CORPUS);
2336
2337      //4.9 cleanup
2338      DBHelper.cleanup(rs);
2339      DBHelper.cleanup(pstmt);
2340
2341      sql = " select lr_id ," +
2342            "         lr_name " +
2343            " from "+Gate.DB_OWNER+".t_document        doc, " +
2344            "      "+Gate.DB_OWNER+".t_lang_resource   lr, " +
2345            "      "+Gate.DB_OWNER+".t_corpus_document corpdoc, " +
2346            "      "+Gate.DB_OWNER+".t_corpus          corp " +
2347            " where lr.lr_id = doc.doc_lr_id " +
2348            "       and doc.doc_id = corpdoc.cd_doc_id " +
2349            "       and corpdoc.cd_corp_id = corp.corp_id " +
2350            "       and corp_lr_id = ? ";
2351      pstmt = this.jdbcConn.prepareStatement(sql);
2352      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2353      pstmt.execute();
2354      rs = pstmt.getResultSet();
2355
2356      //--Vector docLRIDs = new Vector();
2357      Vector documentData = new Vector();
2358      while (rs.next()) {
2359        Long docLRID = new Long(rs.getLong("lr_id"));
2360        String docName = rs.getString("lr_name");
2361        //--docLRIDs.add(docLRID);
2362        documentData.add(new DocumentData(docName, docLRID));
2363      }
2364      DBHelper.cleanup(rs);
2365      DBHelper.cleanup(pstmt);
2366
2367/*
2368      Vector dbDocs = new Vector();
2369      for (int i=0; i< docLRIDs.size(); i++) {
2370        Long currLRID = (Long)docLRIDs.elementAt(i);
2371        //kalina: replaced by a Factory call, so the doc gets registered
2372        //properly in GATE. Otherwise strange behaviour results in the GUI
2373        //and no events come about it
2374//        Document dbDoc = (Document)getLr(DBHelper.DOCUMENT_CLASS,currLRID);
2375        FeatureMap params = Factory.newFeatureMap();
2376        params.put(DataStore.DATASTORE_FEATURE_NAME, this);
2377        params.put(DataStore.LR_ID_FEATURE_NAME, currLRID);
2378        Document dbDoc = (Document)Factory.createResource(DBHelper.DOCUMENT_CLASS, params);
2379
2380
2381        dbDocs.add(dbDoc);
2382      }
2383*/
2384      result = new DatabaseCorpusImpl(lrName,
2385                                      this,
2386                                      (Long)lrPersistenceId,
2387                                      features,
2388  //                                    dbDocs);
2389                                      documentData);
2390    }
2391    catch(SQLException sqle) {
2392      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2393    }
2394    catch(Exception e) {
2395      throw new PersistenceException(e);
2396    }
2397    finally {
2398      DBHelper.cleanup(rs);
2399      DBHelper.cleanup(pstmt);
2400    }
2401
2402    return result;
2403  }
2404
2405
2406  /** helper method for getLR - reads LR of type Document */
2407  private DatabaseDocumentImpl readDocument(Object lrPersistenceId)
2408    throws PersistenceException {
2409
2410    //0. preconditions
2411    Assert.assertNotNull(lrPersistenceId);
2412
2413    if (false == lrPersistenceId instanceof Long) {
2414      throw new IllegalArgumentException();
2415    }
2416
2417    // 1. dummy document to be initialized
2418    DatabaseDocumentImpl result = new DatabaseDocumentImpl(this.jdbcConn);
2419
2420    PreparedStatement pstmt = null;
2421    ResultSet rs = null;
2422
2423    //3. read from DB
2424    try {
2425      String sql = " select lr_name, " +
2426                   "        lrtp_type, " +
2427                   "        lr_id, " +
2428                   "        lr_parent_id, " +
2429                   "        doc_id, " +
2430                   "        doc_url, " +
2431                   "        doc_start, " +
2432                   "        doc_end, " +
2433                   "        doc_is_markup_aware " +
2434                   " from  "+Gate.DB_OWNER+".v_document " +
2435                   " where  lr_id = ? ";
2436
2437      pstmt = this.jdbcConn.prepareStatement(sql);
2438      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2439      pstmt.execute();
2440      rs = pstmt.getResultSet();
2441
2442      if (false == rs.next()) {
2443        //ooops mo data found
2444        throw new PersistenceException("Invalid LR ID supplied - no data found");
2445      }
2446
2447      //4. fill data
2448
2449      //4.0 name
2450      String lrName = rs.getString("lr_name");
2451      Assert.assertNotNull(lrName);
2452      result.setName(lrName);
2453
2454      //4.1 parent
2455      Long parentID = null;
2456      long parent_id = rs.getLong("lr_parent_id");
2457      if (false == rs.wasNull()) {
2458        parentID = new Long(parent_id);
2459
2460        //read parent resource
2461        LanguageResource parentLR = this.getLr(DBHelper.DOCUMENT_CLASS,parentID);
2462        Assert.assertNotNull(parentLR);
2463        Assert.assertTrue(parentLR instanceof DatabaseDocumentImpl);
2464
2465        result.setParent(parentLR);
2466      }
2467
2468
2469      //4.2. markup aware
2470      long markup = rs.getLong("doc_is_markup_aware");
2471      Assert.assertTrue(markup == this.ORACLE_FALSE || markup == this.ORACLE_TRUE);
2472      if (markup == this.ORACLE_FALSE) {
2473        result.setMarkupAware(Boolean.FALSE);
2474      }
2475      else {
2476        result.setMarkupAware(Boolean.TRUE);
2477
2478      }
2479
2480      //4.3 datastore
2481      result.setDataStore(this);
2482
2483      //4.4. persist ID
2484      Long persistID = new Long(rs.getLong("lr_id"));
2485      result.setLRPersistenceId(persistID);
2486
2487      //4.5  source url
2488      String url = rs.getString("doc_url");
2489      result.setSourceUrl(new URL(url));
2490
2491      //4.6. start offset
2492      Long start = null;
2493      long longVal = rs.getLong("doc_start");
2494      //null?
2495      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2496      if (false == rs.wasNull()) {
2497        start = new Long(longVal);
2498      }
2499      result.setSourceUrlStartOffset(start);
2500//      initData.put("DOC_SOURCE_URL_START",start);
2501
2502      //4.7. end offset
2503      Long end = null;
2504      longVal = rs.getLong("doc_end");
2505      //null?
2506      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2507      if (false == rs.wasNull()) {
2508        end = new Long(longVal);
2509      }
2510      result.setSourceUrlEndOffset(end);
2511//      initData.put("DOC_SOURCE_URL_END",end);
2512
2513      //4.8 features
2514      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_DOCUMENT);
2515      result.setFeatures(features);
2516      //initData.put("DOC_FEATURES",features);
2517
2518      //4.9 set the nextAnnotationID correctly
2519      long doc_id = rs.getLong("doc_id");
2520
2521      DBHelper.cleanup(rs);
2522      DBHelper.cleanup(pstmt);
2523      sql = " select  max(ann_local_id),'ann_id'" +
2524            " from "+Gate.DB_OWNER+".t_annotation " +
2525            " where ann_doc_id = ?" +
2526            " union " +
2527            " select max(node_local_id),'node_id' " +
2528            " from "+Gate.DB_OWNER+".t_node " +
2529            " where node_doc_id = ?";
2530
2531      pstmt = this.jdbcConn.prepareStatement(sql);
2532      pstmt.setLong(1,doc_id);
2533      pstmt.setLong(2,doc_id);
2534      pstmt.execute();
2535      rs = pstmt.getResultSet();
2536
2537      int maxAnnID = 0 , maxNodeID = 0;
2538      //ann id
2539      if (false == rs.next()) {
2540        //ooops no data found
2541        throw new PersistenceException("Invalid LR ID supplied - no data found");
2542      }
2543      if (rs.getString(2).equals("ann_id"))
2544        maxAnnID = rs.getInt(1);
2545      else
2546        maxNodeID = rs.getInt(1);
2547
2548      if (false == rs.next()) {
2549        //ooops no data found
2550        throw new PersistenceException("Invalid LR ID supplied - no data found");
2551      }
2552      if (rs.getString(2).equals("node_id"))
2553        maxNodeID = rs.getInt(1);
2554      else
2555        maxAnnID = rs.getInt(1);
2556
2557      result.setNextNodeId(maxNodeID+1);
2558//      initData.put("DOC_NEXT_NODE_ID",new Integer(maxNodeID+1));
2559      result.setNextAnnotationId(maxAnnID+1);
2560//      initData.put("DOC_NEXT_ANN_ID",new Integer(maxAnnID+1));
2561
2562
2563//      params.put("initData__$$__", initData);
2564//      try {
2565        //here we create the persistent LR via Factory, so it's registered
2566        //in GATE
2567//        result = (DatabaseDocumentImpl)Factory.createResource("gate.corpora.DatabaseDocumentImpl", params);
2568//      }
2569//      catch (gate.creole.ResourceInstantiationException ex) {
2570//        throw new GateRuntimeException(ex.getMessage());
2571//      }
2572    }
2573    catch(SQLException sqle) {
2574      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2575    }
2576    catch(Exception e) {
2577      throw new PersistenceException(e);
2578    }
2579    finally {
2580      DBHelper.cleanup(rs);
2581      DBHelper.cleanup(pstmt);
2582    }
2583
2584    return result;
2585  }
2586
2587
2588
2589  /**
2590   *  reads the features of an entity
2591   *  entities are of type LR or Annotation
2592   */
2593  private FeatureMap readFeatures(Long entityID, int entityType)
2594    throws PersistenceException {
2595
2596    //0. preconditions
2597    Assert.assertNotNull(entityID);
2598    Assert.assertTrue(entityType == DBHelper.FEATURE_OWNER_ANNOTATION ||
2599                  entityType == DBHelper.FEATURE_OWNER_CORPUS ||
2600                  entityType == DBHelper.FEATURE_OWNER_DOCUMENT);
2601
2602
2603    PreparedStatement pstmt = null;
2604    ResultSet rs = null;
2605    FeatureMap fm = new SimpleFeatureMapImpl();
2606
2607    //1. read from DB
2608    try {
2609      String sql = " select v2.fk_string, " +
2610                   "        v1.ft_value_type, " +
2611                   "        v1.ft_number_value, " +
2612                   "        v1.ft_binary_value, " +
2613                   "        v1.ft_character_value, " +
2614                   "        v1.ft_long_character_value " +
2615                   " from  "+Gate.DB_OWNER+".t_feature v1, " +
2616                   "       "+Gate.DB_OWNER+".t_feature_key v2 " +
2617                   " where  v1.ft_entity_id = ? " +
2618                   "        and v1.ft_entity_type = ? " +
2619                   "        and v1.ft_key_id = v2.fk_id " +
2620                   " order by v2.fk_string,v1.ft_id";
2621
2622      pstmt = this.jdbcConn.prepareStatement(sql);
2623      pstmt.setLong(1,entityID.longValue());
2624      pstmt.setLong(2,entityType);
2625      pstmt.execute();
2626      rs = pstmt.getResultSet();
2627
2628      //3. fill feature map
2629      Vector arrFeatures = new Vector();
2630      String prevKey = null;
2631      String currKey = null;
2632      Object currFeature = null;
2633
2634
2635      while (rs.next()) {
2636        //NOTE: because there are LOBs in the resulset
2637        //the columns should be read in the order they appear
2638        //in the query
2639        currKey = rs.getString(1);
2640
2641        Long valueType = new Long(rs.getLong(2));
2642
2643        //we don't quite know what is the type of the NUMBER
2644        //stored in DB
2645        Object numberValue = null;
2646
2647        //for all numeric types + boolean -> read from DB as appropriate
2648        //Java object
2649        switch(valueType.intValue()) {
2650
2651          case DBHelper.VALUE_TYPE_BOOLEAN:
2652            numberValue = new Boolean(rs.getBoolean(3));
2653            break;
2654
2655          case DBHelper.VALUE_TYPE_FLOAT:
2656            numberValue = new Float(rs.getFloat(3));
2657            break;
2658
2659          case DBHelper.VALUE_TYPE_INTEGER:
2660            numberValue = new Integer(rs.getInt(3));
2661            break;
2662
2663          case DBHelper.VALUE_TYPE_LONG:
2664            numberValue = new Long(rs.getLong(3));
2665            break;
2666        }
2667
2668        //don't forget to read the rest of the current row
2669        Blob blobValue = rs.getBlob(4);
2670        String stringValue = rs.getString(5);
2671        Clob clobValue = rs.getClob(6);
2672
2673        switch(valueType.intValue()) {
2674
2675          case DBHelper.VALUE_TYPE_NULL:
2676            currFeature = null;
2677            break;
2678
2679          case DBHelper.VALUE_TYPE_BOOLEAN:
2680          case DBHelper.VALUE_TYPE_FLOAT:
2681          case DBHelper.VALUE_TYPE_INTEGER:
2682          case DBHelper.VALUE_TYPE_LONG:
2683            currFeature = numberValue;
2684            break;
2685
2686          case DBHelper.VALUE_TYPE_BINARY:
2687            currFeature = readBLOB(blobValue);
2688            break;
2689
2690          case DBHelper.VALUE_TYPE_STRING:
2691            //this one is tricky too
2692            //if the string is < 4000 bytes long then it's stored as varchar2
2693            //otherwise as CLOB
2694            if (null == stringValue) {
2695              //oops, we got CLOB
2696              StringBuffer temp = new StringBuffer();
2697              readCLOB(clobValue,temp);
2698              currFeature = temp.toString();
2699            }
2700            else {
2701              currFeature = stringValue;
2702            }
2703            break;
2704
2705          default:
2706            throw new PersistenceException("Invalid feature type found in DB, type is ["+valueType.intValue()+"]");
2707        }//switch
2708
2709        //new feature or part of an array?
2710        if (currKey.equals(prevKey) && prevKey != null) {
2711          //part of array
2712          arrFeatures.add(currFeature);
2713        }
2714        else {
2715          //add prev feature to feature map
2716
2717          //is the prev feature an array or a single object?
2718          if (arrFeatures.size() > 1) {
2719            //put a clone, because this is a temp array that will
2720            //be cleared in few lines
2721            fm.put(prevKey, new Vector(arrFeatures));
2722          }
2723          else if (arrFeatures.size() == 1) {
2724            fm.put(prevKey,arrFeatures.elementAt(0));
2725          }
2726          else {
2727            //do nothing, this is the dummy feature
2728            ;
2729          }//if
2730
2731          //now clear the array from previous fesature(s) and put the new
2732          //one there
2733          arrFeatures.clear();
2734
2735          prevKey = currKey;
2736          arrFeatures.add(currFeature);
2737        }//if
2738      }//while
2739
2740      //add the last feature
2741      if (arrFeatures.size() > 1) {
2742        fm.put(currKey,arrFeatures);
2743      }
2744      else if (arrFeatures.size() == 1) {
2745        fm.put(currKey,arrFeatures.elementAt(0));
2746      }
2747    }//try
2748    catch(SQLException sqle) {
2749      throw new PersistenceException("can't read features from DB: ["+ sqle.getMessage()+"]");
2750    }
2751    catch(IOException ioe) {
2752      throw new PersistenceException("can't read features from DB: ["+ ioe.getMessage()+"]");
2753    }
2754    catch(ClassNotFoundException cnfe) {
2755      throw new PersistenceException("can't read features from DB: ["+ cnfe.getMessage()+"]");
2756    }
2757    finally {
2758      DBHelper.cleanup(rs);
2759      DBHelper.cleanup(pstmt);
2760    }
2761
2762    return fm;
2763  }
2764
2765
2766
2767  /**
2768   *  reads the ID of the database
2769   *  every database should have unique string ID
2770   */
2771  protected String readDatabaseID() throws PersistenceException{
2772
2773    PreparedStatement pstmt = null;
2774    ResultSet rs = null;
2775    String  result = null;
2776
2777    //1. read from DB
2778    try {
2779      String sql = " select par_value_string " +
2780                   " from  "+Gate.DB_OWNER+".t_parameter " +
2781                   " where  par_key = ? ";
2782
2783      pstmt = this.jdbcConn.prepareStatement(sql);
2784      pstmt.setString(1,DBHelper.DB_PARAMETER_GUID);
2785      pstmt.execute();
2786      rs = pstmt.getResultSet();
2787
2788      if (false == rs.next()) {
2789        throw new PersistenceException("Can't read database parameter ["+
2790                                          DBHelper.DB_PARAMETER_GUID+"]");
2791      }
2792      result = rs.getString(1);
2793    }
2794    catch(SQLException sqle) {
2795        throw new PersistenceException("Can't read database parameter ["+
2796                                          sqle.getMessage()+"]");
2797    }
2798    finally {
2799      DBHelper.cleanup(rs);
2800      DBHelper.cleanup(pstmt);
2801    }
2802
2803    if (DEBUG) {
2804      Out.println("reult=["+result+"]");
2805    }
2806
2807    return result;
2808  }
2809
2810
2811
2812  /**
2813   *   checks if two databases are identical
2814   *   @see readDatabaseID()
2815   *   NOTE: the same database may be represented by different OracleDataStore instances
2816   *   but the IDs will be the same
2817   */
2818  public boolean equals(Object obj) {
2819
2820    if (false == obj instanceof OracleDataStore) {
2821      return false;
2822    }
2823
2824    OracleDataStore db2 = (OracleDataStore)obj;
2825
2826    if (false == this.getDatabaseID().equals(db2.getDatabaseID())) {
2827      return false;
2828    }
2829
2830    return true;
2831  }
2832
2833
2834
2835  /** helper for sync() - saves a Document in the database */
2836  private void syncDocument(Document doc)
2837    throws PersistenceException, SecurityException {
2838
2839    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2840    Assert.assertTrue(doc.getLRPersistenceId() instanceof Long);
2841
2842    Long lrID = (Long)doc.getLRPersistenceId();
2843    EventAwareLanguageResource dbDoc = (EventAwareLanguageResource)doc;
2844    //1. sync LR
2845    // only name can be changed here
2846    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
2847      _syncLR(doc);
2848    }
2849
2850    //2. sync Document
2851    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_MAIN)) {
2852      _syncDocumentHeader(doc);
2853    }
2854
2855    //3. [optional] sync Content
2856    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_CONTENT)) {
2857      _syncDocumentContent(doc);
2858    }
2859
2860    //4. [optional] sync Features
2861    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
2862      _syncFeatures(doc);
2863    }
2864
2865    //5. [optional] delete from DB named sets that were removed from the document
2866    Collection removedSets = ((EventAwareDocument)dbDoc).getRemovedAnnotationSets();
2867    Collection addedSets = ((EventAwareDocument)dbDoc).getAddedAnnotationSets();
2868    if (false == removedSets.isEmpty() || false == addedSets.isEmpty()) {
2869      _syncAnnotationSets(doc,removedSets,addedSets);
2870    }
2871
2872    //6. [optional] sync Annotations
2873    _syncAnnotations(doc);
2874  }
2875
2876
2877
2878  /**
2879   *  helper for sync()
2880   *  NEVER call directly
2881   */
2882  private void _syncLR(LanguageResource lr)
2883    throws PersistenceException,SecurityException {
2884
2885    //0.preconditions
2886    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
2887                      lr instanceof DatabaseCorpusImpl);;
2888    Assert.assertNotNull(lr.getLRPersistenceId());
2889
2890    CallableStatement stmt = null;
2891
2892    try {
2893      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.update_lr(?,?,?) }");
2894      stmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
2895      stmt.setString(2,lr.getName());
2896      //do we have a parent resource?
2897      if (lr instanceof Document &&
2898          null != lr.getParent()) {
2899        stmt.setLong(3,((Long)lr.getParent().getLRPersistenceId()).longValue());
2900      }
2901      else {
2902        stmt.setNull(3,java.sql.Types.BIGINT);
2903      }
2904
2905      stmt.execute();
2906    }
2907    catch(SQLException sqle) {
2908
2909      switch(sqle.getErrorCode()) {
2910        case DBHelper.X_ORACLE_INVALID_LR:
2911          throw new PersistenceException("can't set LR name in DB: [invalid LR ID]");
2912        default:
2913          throw new PersistenceException(
2914                "can't set LR name in DB: ["+ sqle.getMessage()+"]");
2915      }
2916
2917    }
2918    finally {
2919      DBHelper.cleanup(stmt);
2920    }
2921  }
2922
2923
2924
2925  /** helper for sync() - never call directly */
2926  private void _syncDocumentHeader(Document doc)
2927    throws PersistenceException {
2928
2929    Long lrID = (Long)doc.getLRPersistenceId();
2930
2931    CallableStatement stmt = null;
2932
2933    try {
2934      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
2935                                                    ".persist.update_document(?,?,?,?,?) }");
2936      stmt.setLong(1,lrID.longValue());
2937      stmt.setString(2,doc.getSourceUrl().toString());
2938      //do we have start offset?
2939      if (null==doc.getSourceUrlStartOffset()) {
2940        stmt.setNull(3,java.sql.Types.NUMERIC);
2941      }
2942      else {
2943        stmt.setLong(3,doc.getSourceUrlStartOffset().longValue());
2944      }
2945      //do we have end offset?
2946      if (null==doc.getSourceUrlEndOffset()) {
2947        stmt.setNull(4,java.sql.Types.NUMERIC);
2948      }
2949      else {
2950        stmt.setLong(4,doc.getSourceUrlEndOffset().longValue());
2951      }
2952
2953      stmt.setLong(5,true == doc.getMarkupAware().booleanValue() ? this.ORACLE_TRUE
2954                                                                  : this.ORACLE_FALSE);
2955
2956      stmt.execute();
2957    }
2958    catch(SQLException sqle) {
2959
2960      switch(sqle.getErrorCode()) {
2961        case DBHelper.X_ORACLE_INVALID_LR :
2962          throw new PersistenceException("invalid LR supplied: no such document: ["+
2963                                                            sqle.getMessage()+"]");
2964        default:
2965          throw new PersistenceException("can't change document data: ["+
2966                                                            sqle.getMessage()+"]");
2967      }
2968    }
2969    finally {
2970      DBHelper.cleanup(stmt);
2971    }
2972
2973  }
2974
2975
2976
2977  /** helper for sync() - never call directly */
2978  private void _syncDocumentContent(Document doc)
2979    throws PersistenceException {
2980
2981    PreparedStatement pstmt = null;
2982    ResultSet rs = null;
2983    Long docContID = null;
2984
2985    //1. read from DB
2986    try {
2987      String sql = " select dc_id " +
2988                   " from  "+Gate.DB_OWNER+".v_content " +
2989                   " where  lr_id = ? ";
2990
2991      pstmt = this.jdbcConn.prepareStatement(sql);
2992      pstmt.setLong(1,((Long)doc.getLRPersistenceId()).longValue());
2993      pstmt.execute();
2994      rs = pstmt.getResultSet();
2995
2996      if (false == rs.next()) {
2997        throw new PersistenceException("invalid LR ID supplied");
2998      }
2999
3000      //1, get DC_ID
3001      docContID = new Long(rs.getLong(1));
3002
3003      //2, update LOBs
3004      updateDocumentContent(docContID,doc.getContent());
3005
3006    }
3007    catch(SQLException sqle) {
3008      throw new PersistenceException("Cannot update document content ["+
3009                                      sqle.getMessage()+"]");
3010    }
3011    finally {
3012      DBHelper.cleanup(rs);
3013      DBHelper.cleanup(pstmt);
3014    }
3015  }
3016
3017
3018
3019  /** helper for sync() - never call directly */
3020  private void _syncAddedAnnotations(Document doc, AnnotationSet as, Collection changes)
3021    throws PersistenceException {
3022
3023    //0.preconditions
3024    Assert.assertNotNull(doc);
3025    Assert.assertNotNull(as);
3026    Assert.assertNotNull(changes);
3027    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
3028    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
3029    Assert.assertTrue(changes.size() > 0);
3030
3031
3032    PreparedStatement pstmt = null;
3033    ResultSet rs = null;
3034    CallableStatement cstmt = null;
3035    Long lrID = (Long)doc.getLRPersistenceId();
3036//    Long docID = null;
3037    Long asetID = null;
3038
3039    try {
3040      //1. get the a-set ID in the database
3041      String sql = " select as_id  " +
3042//                   "        as_doc_id " +
3043                   " from  "+Gate.DB_OWNER+".v_annotation_set " +
3044                   " where  lr_id = ? ";
3045      //do we have aset name?
3046      String clause = null;
3047      String name = as.getName();
3048      if (null != name) {
3049        clause =   "        and as_name = ? ";
3050      }
3051      else {
3052        clause =   "        and as_name is null ";
3053      }
3054      sql = sql + clause;
3055
3056      pstmt = this.jdbcConn.prepareStatement(sql);
3057      pstmt.setLong(1,lrID.longValue());
3058      if (null != name) {
3059        pstmt.setString(2,name);
3060      }
3061      pstmt.execute();
3062      rs = pstmt.getResultSet();
3063
3064      if (rs.next()) {
3065        asetID = new Long(rs.getLong("as_id"));
3066//        docID = new Long(rs.getLong("as_doc_id"));
3067//System.out.println("syncing annots, lr_id=["+lrID+"],doc_id=["+docID+"], set_id=["+asetID+"]");
3068      }
3069      else {
3070        throw new PersistenceException("cannot find annotation set with" +
3071                                      " name=["+name+"] , LRID=["+lrID+"] in database");
3072      }
3073
3074      //3. insert the new annotations from this set
3075
3076      //3.1. prepare call
3077      cstmt = this.jdbcConn.prepareCall(
3078              "{ call "+Gate.DB_OWNER+".persist.create_annotation(?,?,?,?,?,?,?,?,?) }");
3079
3080      Long annGlobalID = null;
3081      Iterator it = changes.iterator();
3082
3083      while (it.hasNext()) {
3084
3085        //3.2. insert annotation
3086        Annotation ann = (Annotation)it.next();
3087
3088        Node start = (Node)ann.getStartNode();
3089        Node end = (Node)ann.getEndNode();
3090        String type = ann.getType();
3091
3092        cstmt.setLong(1,lrID.longValue());
3093        cstmt.setLong(2,ann.getId().longValue());
3094        cstmt.setLong(3,asetID.longValue());
3095        cstmt.setLong(4,start.getId().longValue());
3096        cstmt.setLong(5,start.getOffset().longValue());
3097        cstmt.setLong(6,end.getId().longValue());
3098        cstmt.setLong(7,end.getOffset().longValue());
3099        cstmt.setString(8,type);
3100        cstmt.registerOutParameter(9,java.sql.Types.BIGINT);
3101
3102        cstmt.execute();
3103        annGlobalID = new Long(cstmt.getLong(9));
3104
3105        //3.3. set annotation features
3106        FeatureMap features = ann.getFeatures();
3107        Assert.assertNotNull(features);
3108//        createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
3109        createFeaturesBulk(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
3110      }
3111    }
3112    catch(SQLException sqle) {
3113      throw new PersistenceException("can't add annotations in DB : ["+
3114                                      sqle.getMessage()+"]");
3115    }
3116    finally {
3117      DBHelper.cleanup(rs);
3118      DBHelper.cleanup(pstmt);
3119      DBHelper.cleanup(cstmt);
3120    }
3121  }
3122
3123
3124
3125  /** helper for sync() - never call directly */
3126  private void _syncChangedAnnotations(Document doc,AnnotationSet as, Collection changes)
3127    throws PersistenceException {
3128
3129    //technically this approach sux
3130    //at least it works
3131
3132    //1. delete
3133    _syncRemovedAnnotations(doc,as,changes);
3134    //2. recreate
3135    _syncAddedAnnotations(doc,as,changes);
3136  }
3137
3138
3139  /** helper for sync() - never call directly */
3140  private void _syncRemovedDocumentsFromCorpus(List docLRIDs, Long corpLRID)
3141    throws PersistenceException {
3142
3143    //0.preconditions
3144    Assert.assertNotNull(docLRIDs);
3145    Assert.assertNotNull(corpLRID);
3146    Assert.assertTrue(docLRIDs.size() > 0);
3147
3148    CallableStatement cstmt = null;
3149
3150    try {
3151      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
3152                                                ".persist.remove_document_from_corpus(?,?) }");
3153
3154      Iterator it = docLRIDs.iterator();
3155      while (it.hasNext()) {
3156        Long currLRID = (Long)it.next();
3157        cstmt.setLong(1,currLRID.longValue());
3158        cstmt.setLong(2,corpLRID.longValue());
3159        cstmt.execute();
3160      }
3161    }
3162    catch(SQLException sqle) {
3163
3164      switch(sqle.getErrorCode()) {
3165        case DBHelper.X_ORACLE_INVALID_LR :
3166          throw new PersistenceException("invalid LR supplied: no such document: ["+
3167                                                            sqle.getMessage()+"]");
3168        default:
3169          throw new PersistenceException("can't change document data: ["+
3170                                                            sqle.getMessage()+"]");
3171      }
3172    }
3173    finally {
3174      DBHelper.cleanup(cstmt);
3175    }
3176
3177  }
3178
3179
3180  /** helper for sync() - never call directly */
3181  private void _syncRemovedAnnotations(Document doc,AnnotationSet as, Collection changes)
3182    throws PersistenceException {
3183    //0.preconditions
3184    Assert.assertNotNull(doc);
3185    Assert.assertNotNull(as);
3186    Assert.assertNotNull(changes);
3187    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
3188    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
3189    Assert.assertTrue(changes.size() > 0);
3190
3191
3192    PreparedStatement pstmt = null;
3193    ResultSet rs = null;
3194    CallableStatement cstmt = null;
3195    Long lrID = (Long)doc.getLRPersistenceId();
3196    Long docID = null;
3197    Long asetID = null;
3198
3199    try {
3200      //1. get the a-set ID in the database
3201      String sql = " select as_id,  " +
3202                   "        as_doc_id " +
3203                   " from  "+Gate.DB_OWNER+".v_annotation_set " +
3204                   " where  lr_id = ? ";
3205      //do we have aset name?
3206      String clause = null;
3207      String name = as.getName();
3208      if (null != name) {
3209        clause =   "        and as_name = ? ";
3210      }
3211      else {
3212        clause =   "        and as_name is null ";
3213      }
3214      sql = sql + clause;
3215
3216      pstmt = this.jdbcConn.prepareStatement(sql);
3217      pstmt.setLong(1,lrID.longValue());
3218      if (null != name) {
3219        pstmt.setString(2,name);
3220      }
3221      pstmt.execute();
3222      rs = pstmt.getResultSet();
3223
3224      if (rs.next()) {
3225        asetID = new Long(rs.getLong("as_id"));
3226        docID = new Long(rs.getLong("as_doc_id"));
3227      }
3228      else {
3229        throw new PersistenceException("cannot find annotation set with" +
3230                                      " name=["+name+"] , LRID=["+lrID+"] in database");
3231      }
3232
3233      //3. delete the removed annotations from this set
3234
3235      //3.1. prepare call
3236      cstmt = this.jdbcConn.prepareCall(
3237              "{ call "+Gate.DB_OWNER+".persist.delete_annotation(?,?) }");
3238
3239
3240      Iterator it = changes.iterator();
3241
3242      while (it.hasNext()) {
3243
3244        //3.2. insert annotation
3245        Annotation ann = (Annotation)it.next();
3246
3247        cstmt.setLong(1,docID.longValue()); //annotations are linked with documents, not LRs!
3248        cstmt.setLong(2,ann.getId().longValue());
3249        cstmt.execute();
3250      }
3251    }
3252    catch(SQLException sqle) {
3253      throw new PersistenceException("can't delete annotations in DB : ["+
3254                                      sqle.getMessage()+"]");
3255    }
3256    finally {
3257      DBHelper.cleanup(rs);
3258      DBHelper.cleanup(pstmt);
3259      DBHelper.cleanup(cstmt);
3260    }
3261  }
3262
3263
3264
3265  /** helper for sync() - never call directly */
3266  private void _syncAnnotationSets(Document doc,Collection removedSets,Collection addedSets)
3267    throws PersistenceException {
3268
3269    //0. preconditions
3270    Assert.assertNotNull(doc);
3271    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
3272    Assert.assertNotNull(doc.getLRPersistenceId());
3273    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
3274                      this.getDatabaseID());
3275    Assert.assertNotNull(removedSets);
3276    Assert.assertNotNull(addedSets);
3277
3278    Long lrID = (Long)doc.getLRPersistenceId();
3279
3280    //1. delete from DB removed a-sets
3281    CallableStatement cstmt = null;
3282
3283    try {
3284      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
3285                                                ".persist.delete_annotation_set(?,?) }");
3286
3287      Iterator it = removedSets.iterator();
3288      while (it.hasNext()) {
3289        String setName = (String)it.next();
3290        cstmt.setLong(1,lrID.longValue());
3291        cstmt.setString(2,setName);
3292        cstmt.execute();
3293      }
3294    }
3295    catch(SQLException sqle) {
3296      throw new PersistenceException("can't remove annotation set from DB: ["+ sqle.getMessage()+"]");
3297    }
3298    finally {
3299      DBHelper.cleanup(cstmt);
3300    }
3301
3302    //2. create in DB new a-sets
3303    Iterator it = addedSets.iterator();
3304    while (it.hasNext()) {
3305      String setName = (String)it.next();
3306      AnnotationSet aset = doc.getAnnotations(setName);
3307
3308      Assert.assertNotNull(aset);
3309      Assert.assertTrue(aset instanceof DatabaseAnnotationSetImpl);
3310
3311      createAnnotationSet(lrID,aset);
3312    }
3313  }
3314
3315
3316
3317  /** helper for sync() - never call directly */
3318  private void _syncAnnotations(Document doc)
3319    throws PersistenceException {
3320
3321    //0. preconditions
3322    Assert.assertNotNull(doc);
3323    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
3324    Assert.assertNotNull(doc.getLRPersistenceId());
3325    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
3326                      this.getDatabaseID());
3327
3328
3329    EventAwareDocument ead = (EventAwareDocument)doc;
3330    //1. get the sets read from the DB for this document
3331    //chnaged annotations can occur only in such sets
3332    Collection loadedSets = ead.getLoadedAnnotationSets();
3333
3334    Iterator it = loadedSets.iterator();
3335    while (it.hasNext()) {
3336      AnnotationSet as = (AnnotationSet)it.next();
3337      //check that this set is neither NEW nor DELETED
3338      //they should be already synced
3339      if (ead.getAddedAnnotationSets().contains(as.getName()) ||
3340          ead.getRemovedAnnotationSets().contains(as.getName())) {
3341        //oops, ignore it
3342        continue;
3343      }
3344
3345      EventAwareAnnotationSet eas = (EventAwareAnnotationSet)as;
3346      Assert.assertNotNull(as);
3347
3348      Collection anns = null;
3349      anns = eas.getAddedAnnotations();
3350      Assert.assertNotNull(anns);
3351      if (anns.size()>0) {
3352        _syncAddedAnnotations(doc,as,anns);
3353      }
3354
3355      anns = eas.getRemovedAnnotations();
3356      Assert.assertNotNull(anns);
3357      if (anns.size()>0) {
3358        _syncRemovedAnnotations(doc,as,anns);
3359      }
3360
3361      anns = eas.getChangedAnnotations();
3362      Assert.assertNotNull(anns);
3363      if (anns.size()>0) {
3364        _syncChangedAnnotations(doc,as,anns);
3365      }
3366    }
3367  }
3368
3369
3370
3371  /** helper for sync() - never call directly */
3372  private void _syncFeatures(LanguageResource lr)
3373    throws PersistenceException {
3374
3375    //0. preconditions
3376    Assert.assertNotNull(lr);
3377    Assert.assertNotNull(lr.getLRPersistenceId());
3378    Assert.assertEquals(((DatabaseDataStore)lr.getDataStore()).getDatabaseID(),
3379                      this.getDatabaseID());
3380    Assert.assertTrue(lr instanceof Document || lr instanceof Corpus);
3381    //we have to be in the context of transaction
3382
3383    //1, get ID  in the DB
3384    Long lrID = (Long)lr.getLRPersistenceId();
3385    int  entityType;
3386
3387    //2. delete features
3388    CallableStatement stmt = null;
3389    try {
3390      Assert.assertTrue(false == this.jdbcConn.getAutoCommit());
3391      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
3392                                                    ".persist.delete_features(?,?) }");
3393      stmt.setLong(1,lrID.longValue());
3394
3395      if (lr instanceof Document) {
3396        entityType = DBHelper.FEATURE_OWNER_DOCUMENT;
3397      }
3398      else if (lr instanceof Corpus) {
3399        entityType = DBHelper.FEATURE_OWNER_CORPUS;
3400      }
3401      else {
3402        throw new IllegalArgumentException();
3403      }
3404
3405      stmt.setInt(2,entityType);
3406      stmt.execute();
3407    }
3408    catch(SQLException sqle) {
3409      throw new PersistenceException("can't delete features in DB: ["+ sqle.getMessage()+"]");
3410    }
3411    finally {
3412      DBHelper.cleanup(stmt);
3413    }
3414
3415    //3. recreate them
3416    //createFeatures(lrID,entityType, lr.getFeatures());
3417    createFeaturesBulk(lrID,entityType, lr.getFeatures());
3418
3419  }
3420
3421
3422
3423  /** helper for sync() - saves a Corpus in the database */
3424  private void syncCorpus(Corpus corp)
3425    throws PersistenceException,SecurityException {
3426
3427    //0. preconditions
3428    Assert.assertNotNull(corp);
3429    Assert.assertTrue(corp instanceof DatabaseCorpusImpl);
3430    Assert.assertEquals(this,corp.getDataStore());
3431    Assert.assertNotNull(corp.getLRPersistenceId());
3432
3433    EventAwareCorpus dbCorpus = (EventAwareCorpus)corp;
3434
3435    //1. sync the corpus name?
3436    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
3437      _syncLR(corp);
3438    }
3439
3440    //2. sync the corpus features?
3441    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
3442      _syncFeatures(corp);
3443    }
3444
3445    //2.5 get removed documents and detach (not remove) them from the corpus in the
3446    //database
3447    List removedDocLRIDs = dbCorpus.getRemovedDocuments();
3448    if (removedDocLRIDs.size() > 0) {
3449      _syncRemovedDocumentsFromCorpus(removedDocLRIDs,(Long)corp.getLRPersistenceId());
3450    }
3451
3452    //3. get all documents
3453    //--Iterator it = corp.iterator();
3454    Iterator it = dbCorpus.getLoadedDocuments().iterator();
3455
3456    while (it.hasNext()) {
3457      Document dbDoc = (Document)it.next();
3458      //note - document may be NULL which means it was not loaded (load on demand)
3459      //just ignore it then
3460      if (null == dbDoc) {
3461        continue;
3462      }
3463
3464      //adopt/sync?
3465      if (null == dbDoc.getLRPersistenceId()) {
3466        //doc was never adopted, adopt it
3467
3468        //3.1 remove the transient doc from the corpus
3469        it.remove();
3470
3471        //3.2 get the security info for the corpus
3472        SecurityInfo si = getSecurityInfo(corp);
3473
3474
3475        Document adoptedDoc = null;
3476        try {
3477          //3.3. adopt the doc with the sec info
3478//System.out.println("adopting ["+dbDoc.getName()+"] ...");
3479          //don't open a new transaction, since sync() already has opended one
3480          adoptedDoc = (Document)_adopt(dbDoc,si,true);
3481
3482          //3.4. add doc to corpus in DB
3483          addDocumentToCorpus((Long)adoptedDoc.getLRPersistenceId(),
3484                              (Long)corp.getLRPersistenceId());
3485        }
3486        catch(SecurityException se) {
3487          throw new PersistenceException(se);
3488        }
3489
3490        //3.5 add back to corpus the new DatabaseDocument
3491        corp.add(adoptedDoc);
3492      }
3493      else {
3494        //don't open a new transaction, the sync() called for corpus has already
3495        //opened one
3496        try {
3497          _sync(dbDoc,true);
3498
3499          // let the world know about it
3500          fireResourceWritten( new DatastoreEvent(this,
3501                                                  DatastoreEvent.RESOURCE_WRITTEN,
3502                                                  dbDoc,
3503                                                  dbDoc.getLRPersistenceId()
3504                                                  )
3505                              );
3506
3507          //if the document is form the same DS but did not belong to the corpus add it now
3508          //NOTE: if the document already belongs to the corpus then nothing will be changed
3509          //in the DB
3510          addDocumentToCorpus((Long)dbDoc.getLRPersistenceId(),
3511                              (Long)corp.getLRPersistenceId());
3512        }
3513        catch(SecurityException se) {
3514          gate.util.Err.prln("document cannot be synced: ["+se.getMessage()+"]");
3515        }
3516      }
3517    }
3518  }
3519
3520
3521
3522  /**
3523   * Try to acquire exlusive lock on a resource from the persistent store.
3524   * Always call unlockLR() when the lock is no longer needed
3525   */
3526  public boolean lockLr(LanguageResource lr)
3527  throws PersistenceException,SecurityException {
3528
3529    //0. preconditions
3530    Assert.assertNotNull(lr);
3531    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
3532                      lr instanceof DatabaseCorpusImpl);
3533    Assert.assertNotNull(lr.getLRPersistenceId());
3534    Assert.assertEquals(lr.getDataStore(),this);
3535
3536    //1. delegate
3537    return _lockLr((Long)lr.getLRPersistenceId());
3538  }
3539
3540
3541
3542  /**
3543   *  helper for lockLR()
3544   *  never call directly
3545   */
3546  private boolean _lockLr(Long lrID)
3547  throws PersistenceException,SecurityException {
3548
3549    //0. preconditions
3550    Assert.assertNotNull(lrID);
3551
3552    //1. check session
3553    if (null == this.session) {
3554      throw new SecurityException("session not set");
3555    }
3556
3557    if (false == this.ac.isValidSession(this.session)) {
3558      throw new SecurityException("invalid session supplied");
3559    }
3560
3561    //2. check permissions
3562    if (false == canWriteLR(lrID)) {
3563      throw new SecurityException("no write access granted to the user");
3564    }
3565
3566    //3. try to lock
3567    CallableStatement cstmt = null;
3568    boolean lockSucceeded = false;
3569
3570    try {
3571      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.lock_lr(?,?,?,?) }");
3572      cstmt.setLong(1,lrID.longValue());
3573      cstmt.setLong(2,this.session.getUser().getID().longValue());
3574      cstmt.setLong(3,this.session.getGroup().getID().longValue());
3575      cstmt.registerOutParameter(4,java.sql.Types.NUMERIC);
3576      cstmt.execute();
3577
3578      lockSucceeded = cstmt.getLong(4) == this.ORACLE_TRUE
3579                                          ? true
3580                                          : false;
3581    }
3582    catch(SQLException sqle) {
3583
3584      switch(sqle.getErrorCode()) {
3585        case DBHelper.X_ORACLE_INVALID_LR:
3586          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
3587        default:
3588          throw new PersistenceException(
3589                "can't lock LR in DB : ["+ sqle.getMessage()+"]");
3590      }
3591    }
3592    finally {
3593      DBHelper.cleanup(cstmt);
3594    }
3595
3596    return lockSucceeded;
3597  }
3598
3599
3600
3601  /**
3602   * Releases the exlusive lock on a resource from the persistent store.
3603   */
3604  public void unlockLr(LanguageResource lr)
3605  throws PersistenceException,SecurityException {
3606
3607    //0. preconditions
3608    Assert.assertNotNull(lr);
3609    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
3610                      lr instanceof DatabaseCorpusImpl);
3611    Assert.assertNotNull(lr.getLRPersistenceId());
3612    Assert.assertEquals(lr.getDataStore(),this);
3613
3614    //1. check session
3615    if (null == this.session) {
3616      throw new SecurityException("session not set");
3617    }
3618
3619    if (false == this.ac.isValidSession(this.session)) {
3620      throw new SecurityException("invalid session supplied");
3621    }
3622
3623    //2. check permissions
3624    if (false == canWriteLR(lr.getLRPersistenceId())) {
3625      throw new SecurityException("no write access granted to the user");
3626    }
3627
3628    //3. try to unlock
3629    CallableStatement cstmt = null;
3630    boolean lockSucceeded = false;
3631
3632    try {
3633      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.unlock_lr(?,?) }");
3634      cstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
3635      cstmt.setLong(2,this.session.getUser().getID().longValue());
3636      cstmt.execute();
3637    }
3638    catch(SQLException sqle) {
3639
3640      switch(sqle.getErrorCode()) {
3641        case DBHelper.X_ORACLE_INVALID_LR:
3642          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
3643        default:
3644          throw new PersistenceException(
3645                "can't unlock LR in DB : ["+ sqle.getMessage()+"]");
3646      }
3647    }
3648    finally {
3649      DBHelper.cleanup(cstmt);
3650    }
3651  }
3652
3653
3654
3655  /**
3656   * Releases the exlusive lock on a resource from the persistent store.
3657   */
3658  private User getLockingUser(LanguageResource lr)
3659    throws PersistenceException,SecurityException {
3660
3661    //0. preconditions
3662    Assert.assertNotNull(lr);
3663    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
3664                      lr instanceof DatabaseCorpusImpl);
3665    Assert.assertNotNull(lr.getLRPersistenceId());
3666    Assert.assertEquals(lr.getDataStore(),this);
3667
3668    //delegate
3669    return getLockingUser((Long)lr.getLRPersistenceId());
3670  }
3671
3672
3673
3674  /**
3675   * Releases the exlusive lock on a resource from the persistent store.
3676   */
3677  private User getLockingUser(Long lrID)
3678  throws PersistenceException,SecurityException {
3679
3680    //1. check session
3681    if (null == this.session) {
3682      throw new SecurityException("session not set");
3683    }
3684
3685    if (false == this.ac.isValidSession(this.session)) {
3686      throw new SecurityException("invalid session supplied");
3687    }
3688
3689    //3. read from DB
3690    PreparedStatement pstmt = null;
3691    Long userID = null;
3692    ResultSet rs = null;
3693
3694    try {
3695      String sql = "  select  nvl(lr_locking_user_id,0) as user_id" +
3696                    " from "+Gate.DB_OWNER+".t_lang_resource " +
3697                    " where   lr_id = ?";
3698
3699      pstmt = this.jdbcConn.prepareStatement(sql);
3700      pstmt.setLong(1,lrID.longValue());
3701      pstmt.execute();
3702      rs = pstmt.getResultSet();
3703
3704      if (false == rs.next()) {
3705        throw new PersistenceException("LR not found in DB");
3706      }
3707
3708      long result = rs.getLong("user_id");
3709
3710      return result == 0  ? null
3711                          : this.ac.findUser(new Long(result));
3712    }
3713    catch(SQLException sqle) {
3714      throw new PersistenceException("can't get locking user from DB : ["+ sqle.getMessage()+"]");
3715    }
3716    finally {
3717      DBHelper.cleanup(rs);
3718      DBHelper.cleanup(pstmt);
3719    }
3720  }
3721
3722
3723
3724  /**
3725   *   adds document to corpus in the database
3726   *   if the document is already part of the corpus nothing
3727   *   changes
3728   */
3729  private void addDocumentToCorpus(Long docID,Long corpID)
3730  throws PersistenceException,SecurityException {
3731
3732    //0. preconditions
3733    Assert.assertNotNull(docID);
3734    Assert.assertNotNull(corpID);
3735
3736    //1. check session
3737    if (null == this.session) {
3738      throw new SecurityException("session not set");
3739    }
3740
3741    if (false == this.ac.isValidSession(this.session)) {
3742      throw new SecurityException("invalid session supplied");
3743    }
3744
3745    //2. check permissions
3746    if (false == canWriteLR(corpID)) {
3747      throw new SecurityException("no write access granted to the user");
3748    }
3749
3750    if (false == canWriteLR(docID)) {
3751      throw new SecurityException("no write access granted to the user");
3752    }
3753
3754    //3. database
3755    CallableStatement cstmt = null;
3756
3757    try {
3758      cstmt = this.jdbcConn.prepareCall("{ call "+
3759                                  Gate.DB_OWNER+".persist.add_document_to_corpus(?,?) }");
3760      cstmt.setLong(1,docID.longValue());
3761      cstmt.setLong(2,corpID.longValue());
3762      cstmt.execute();
3763    }
3764    catch(SQLException sqle) {
3765
3766      switch(sqle.getErrorCode()) {
3767        case DBHelper.X_ORACLE_INVALID_LR:
3768          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
3769        default:
3770          throw new PersistenceException(
3771                "can't add document to corpus : ["+ sqle.getMessage()+"]");
3772      }
3773    }
3774    finally {
3775      DBHelper.cleanup(cstmt);
3776    }
3777  }
3778
3779
3780
3781  /**
3782   *   unloads a LR from the GUI
3783   */
3784  private void unloadLR(Long lrID)
3785  throws GateException{
3786
3787    //0. preconfitions
3788    Assert.assertNotNull(lrID);
3789
3790    //1. get all LRs in the system
3791    List resources = Gate.getCreoleRegister().getAllInstances("gate.LanguageResource");
3792
3793    Iterator it = resources.iterator();
3794    while (it.hasNext()) {
3795      LanguageResource lr = (LanguageResource)it.next();
3796      if (lrID.equals(lr.getLRPersistenceId()) &&
3797          this.equals(lr.getDataStore())) {
3798        //found it - unload it
3799        Factory.deleteResource(lr);
3800        break;
3801      }
3802    }
3803  }
3804
3805    /** Get a list of LRs that satisfy some set or restrictions
3806     *
3807     *  @param constraints list of Restriction objects
3808     */
3809  public List findLrIds(List constraints) throws PersistenceException {
3810    return findLrIds(constraints,null);
3811  }
3812
3813  /**
3814   *  Get a list of LRs IDs that satisfy some set or restrictions and are
3815   *  of a particular type
3816   *
3817   * @param constraints list of Restriction objects
3818   * @param lrType type of Lrs. DBHelper.DOCUMENT_CLASS or DBHelper.CORPUS_CLASS
3819   */
3820  public List findLrIds(List constraints, String lrType) throws PersistenceException {
3821    return findLrIds(constraints, lrType, null, -1);
3822  }
3823
3824  /**
3825   *  Get a list of LRs IDs that satisfy some set or restrictions and are
3826   *  of a particular type
3827   *
3828   * @param constraints list of Restriction objects
3829   * @param lrType type of Lrs. DBHelper.DOCUMENT_CLASS or DBHelper.CORPUS_CLASS
3830   * @param orderByConstraints liat of OrderByRestriction objects
3831   * @param limitcount limit returning objects -1 for unlimited
3832   */
3833  public List findLrIds(List constraints, String lrType,
3834                      List orderByConstraints, int limitcount) throws PersistenceException {
3835      Vector lrsIDs = new Vector();
3836      CallableStatement stmt = null;
3837      ResultSet rs = null;
3838      Connection conn = null;
3839
3840      try {
3841        Vector sqlValues = new Vector();
3842        String sql = getSQLQuery(constraints, lrType, false, orderByConstraints, limitcount, sqlValues);
3843        conn = DBHelper.connect(this.getStorageUrl(), true);
3844        stmt = conn.prepareCall(sql);
3845        //System.out.println(sql);
3846        for (int i = 0; i<sqlValues.size(); i++){
3847          if (sqlValues.elementAt(i) instanceof String){
3848            stmt.setString(i+1,sqlValues.elementAt(i).toString());
3849          }
3850          else if (sqlValues.elementAt(i) instanceof Long){
3851            stmt.setLong(i+1,((Long) sqlValues.elementAt(i)).longValue());
3852          }
3853          else if (sqlValues.elementAt(i) instanceof Integer){
3854            stmt.setLong(i+1,((Integer) sqlValues.elementAt(i)).intValue());
3855          }
3856        }
3857        stmt.execute();
3858        rs = stmt.getResultSet();
3859
3860        while (rs.next()) {
3861          long lr_ID = rs.getLong(1);
3862          lrsIDs.addElement(new Long(lr_ID));
3863        }
3864        return lrsIDs;
3865      }
3866      catch(SQLException sqle) {
3867        throw new PersistenceException("can't get LRs from DB: ["+ sqle+"]");
3868      }
3869      catch (ClassNotFoundException cnfe){
3870        throw new PersistenceException("can't not find driver: ["+ cnfe +"]");
3871      }
3872      finally {
3873        DBHelper.cleanup(rs);
3874        DBHelper.cleanup(stmt);
3875        DBHelper.disconnect(conn, true);
3876      }
3877    }
3878  /**
3879   * Return count of LRs which matches the constraints.
3880   *
3881   * @param constraints list of Restriction objects
3882   * @param lrType type of Lrs. DBHelper.DOCUMENT_CLASS or DBHelper.CORPUS_CLASS
3883   */
3884  public long getLrsCount(List constraints, String lrType) throws PersistenceException {
3885      Vector lrs = new Vector();
3886      CallableStatement stmt = null;
3887      ResultSet rs = null;
3888      Connection conn = null;
3889
3890      try {
3891        Vector sqlValues = new Vector();
3892        String sql = getSQLQuery(constraints,lrType, true, null, -1, sqlValues);
3893        conn = DBHelper.connect(this.getStorageUrl(), true);
3894        stmt = conn.prepareCall(sql);
3895        for (int i = 0; i<sqlValues.size(); i++){
3896          if (sqlValues.elementAt(i) instanceof String){
3897            stmt.setString(i+1,sqlValues.elementAt(i).toString());
3898          }
3899          else if (sqlValues.elementAt(i) instanceof Long){
3900            stmt.setLong(i+1,((Long) sqlValues.elementAt(i)).longValue());
3901          }
3902          else if (sqlValues.elementAt(i) instanceof Integer){
3903            stmt.setLong(i+1,((Integer) sqlValues.elementAt(i)).intValue());
3904          }
3905        }
3906
3907        stmt.execute();
3908        rs = stmt.getResultSet();
3909        rs.next();
3910        return rs.getLong(1);
3911      }
3912      catch(SQLException sqle) {
3913        throw new PersistenceException("can't get LRs Count from DB: ["+ sqle+"]");
3914      }
3915      catch (ClassNotFoundException cnfe){
3916        throw new PersistenceException("can't not find driver: ["+ cnfe +"]");
3917      }
3918      finally {
3919        DBHelper.cleanup(rs);
3920        DBHelper.cleanup(stmt);
3921        DBHelper.disconnect(conn, true);
3922      }
3923  }
3924
3925  private String getSQLQuery(List filter, String lrType, boolean count,
3926                              List orderByFilter, int limitcount, Vector sqlValues){
3927    StringBuffer query = new StringBuffer("");
3928    String join = getJoinQuery(orderByFilter, sqlValues);
3929    String select = "lr_id";
3930    if (count){
3931      select = "count(*)";
3932    }
3933
3934    query = query.append(" SELECT " + select + " " +
3935                          " FROM  "+Gate.DB_OWNER+".t_lang_resource LR " + join +
3936                          " WHERE ");
3937
3938    if (lrType != null){
3939      query = query.append(" LR.lr_type_id = ? ");
3940      if (lrType.equals(DBHelper.CORPUS_CLASS)) {
3941        sqlValues.addElement(new Long(2));
3942      }// if DBHelper.CORPUS_CLASS
3943      if (lrType.equals(DBHelper.DOCUMENT_CLASS)) {
3944        sqlValues.addElement(new Long(1));
3945      }// if DBHelper.DOCUMENT_CLASS
3946    }
3947
3948    if (filter!=null && filter.size()>0){
3949      if (lrType!=null){
3950        query = query.append(" AND ");
3951      }
3952      for (int i=0; i<filter.size(); i++){
3953          query = query.append(getRestrictionPartOfQuery((Restriction) filter.get(i),sqlValues));
3954          if (i<filter.size()-1) {
3955            query = query.append(" AND ");
3956          }
3957      }
3958    }
3959
3960    String endPartOfJoin = getEndPartOfJoin(orderByFilter,sqlValues);
3961    query = query.append(endPartOfJoin);
3962
3963    if (limitcount>0){
3964      query = query.insert(0,"select lr_id from ( ");
3965      query = query.append( ") where rownum<"+limitcount);
3966    }
3967
3968    return query.toString();
3969  }
3970
3971  private String getRestrictionPartOfQuery(Restriction restr, Vector sqlValues){
3972    StringBuffer expresion = new StringBuffer(
3973                      " EXISTS ("+
3974                       " SELECT ft_id " +
3975                       " FROM "+Gate.DB_OWNER+".t_feature FEATURE, " +
3976                       Gate.DB_OWNER + ".t_feature_key FTK" +
3977                       " WHERE FEATURE.ft_entity_id = LR.lr_id ");
3978
3979    if (restr.getKey() != null){
3980      expresion = expresion.append(" AND FTK.fk_id = FEATURE.ft_key_id ");
3981      expresion = expresion.append(" AND FTK.fk_string = ? ");
3982      sqlValues.addElement(restr.getKey());
3983    }
3984
3985    if (restr.getValue() != null){
3986      expresion = expresion.append(" AND ");
3987      switch (this.findFeatureType(restr.getValue())){
3988        case DBHelper.VALUE_TYPE_INTEGER:
3989          expresion = expresion.append(getNumberExpresion(restr, sqlValues));
3990          break;
3991        case DBHelper.VALUE_TYPE_LONG:
3992          expresion = expresion.append(getNumberExpresion(restr, sqlValues));
3993          break;
3994        default:
3995          if (restr.getOperator()==Restriction.OPERATOR_EQUATION){
3996            expresion = expresion.append(" FEATURE.ft_character_value = ? ");
3997            sqlValues.addElement(restr.getStringValue());
3998          }
3999          if (restr.getOperator()==Restriction.OPERATOR_LIKE){
4000            expresion = expresion.append(" upper(FEATURE.ft_character_value) like ? ");
4001            sqlValues.addElement("%"+restr.getStringValue().toUpperCase()+"%");
4002          }
4003          break;
4004      }
4005    }
4006
4007    expresion = expresion.append(" )");
4008
4009    return expresion.toString();
4010  }
4011
4012  private String getNumberExpresion(Restriction restr, Vector sqlValues){
4013    StringBuffer expr = new StringBuffer("FEATURE.ft_number_value ");
4014
4015    switch (restr.getOperator()){
4016      case Restriction.OPERATOR_EQUATION:
4017        expr = expr.append(" = ");
4018        break;
4019      case Restriction.OPERATOR_BIGGER:
4020        expr = expr.append("  > ");
4021        break;
4022      case Restriction.OPERATOR_LESS:
4023        expr = expr.append(" < ");
4024        break;
4025      case Restriction.OPERATOR_EQUATION_OR_BIGGER:
4026        expr = expr.append(" >= ");
4027        break;
4028      case Restriction.OPERATOR_EQUATION_OR_LESS:
4029        expr = expr.append(" <= ");
4030        break;
4031      default:
4032        return " 0 = 0 ";
4033    }
4034
4035    expr.append(" ? ");
4036    sqlValues.addElement(restr.getValue());
4037
4038    return expr.toString();
4039  }
4040
4041  private String getJoinQuery(List orderByFilter, Vector sqlValues){
4042    StringBuffer join = new StringBuffer("");
4043    if (orderByFilter!=null){
4044      for (int i = 0; i<orderByFilter.size(); i++){
4045        join = join.append(" , "+Gate.DB_OWNER+".t_feature FT"+i);
4046        join = join.append(" , "+Gate.DB_OWNER+".t_feature_key FTK"+i);
4047      }
4048    }
4049    return join.toString();
4050  }
4051
4052  private String getEndPartOfJoin(List orderByFilter, Vector sqlValues){
4053    StringBuffer endJoin = new StringBuffer("");
4054    if (orderByFilter!=null && orderByFilter.size()>0){
4055      for (int i=0; i<orderByFilter.size(); i++){
4056        endJoin = endJoin.append(" and lr_id=FT"+i+".ft_entity_id ");
4057        endJoin = endJoin.append(" and  FT"+i+".ft_key_id = FTK"+i+".fk_id ");
4058        endJoin = endJoin.append(" and  FTK"+i+".fk_string= ? ");
4059        OrderByRestriction restr = (OrderByRestriction) orderByFilter.get(i);
4060        sqlValues.addElement(restr.getKey());
4061      }
4062      endJoin = endJoin.append(" order by ");
4063      for (int i=0; i<orderByFilter.size(); i++){
4064        OrderByRestriction restr = (OrderByRestriction) orderByFilter.get(i);
4065        endJoin = endJoin.append("  FT"+i+".ft_number_value ");
4066        if (restr.getOperator()==OrderByRestriction.OPERATOR_ASCENDING){
4067          endJoin = endJoin.append(" asc, ");
4068        } else {
4069          endJoin = endJoin.append(" desc, ");
4070        }
4071        endJoin = endJoin.append("  FT"+i+".ft_character_value ");
4072        if (restr.getOperator()==OrderByRestriction.OPERATOR_ASCENDING){
4073          endJoin = endJoin.append(" asc ");
4074        } else {
4075          endJoin = endJoin.append(" desc ");
4076        }
4077        if (i<orderByFilter.size()-1){
4078          endJoin = endJoin.append(" , ");
4079        }
4080      }
4081    }
4082    return endJoin.toString();
4083  }
4084
4085
4086  private class Feature {
4087
4088    Long entityID;
4089    int entityType;
4090    String key;
4091    Object value;
4092    int valueType;
4093
4094    public Feature(Long eid, int eType, String key, Object value, int vType) {
4095
4096      this.entityID = eid;
4097      this.entityType = eType;
4098      this.key = key;
4099      this.value = value;
4100      this.valueType = vType;
4101    }
4102  }
4103
4104}
4105
4106