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.114 2001/11/30 12:54:13 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.CLOB;
24  import oracle.sql.BLOB;
25  
26  import junit.framework.*;
27  
28  import gate.*;
29  import gate.util.*;
30  import gate.event.*;
31  import gate.security.*;
32  import gate.security.SecurityException; //hide the more general exception
33  import gate.corpora.*;
34  import gate.annotation.*;
35  import gate.creole.ResourceData;
36  
37  public class OracleDataStore extends JDBCDataStore {
38  
39    /** Name of this resource */
40    public static final String DS_COMMENT = "GATE Oracle datastore";
41  
42    /** the icon for this resource */
43    public static final String DS_ICON_NAME = "ds.gif";
44  
45    /** Debug flag */
46    private static final boolean DEBUG = false;
47  
48    /** "true" value for Oracle (supports no boolean type) */
49    private static final int ORACLE_TRUE = 1;
50    /** "false" value for Oracle (supports no boolean type) */
51    private static final int ORACLE_FALSE = 0;
52  
53    /** used internaly, may change in the future */
54    private static final int READ_ACCESS = 0;
55    /** used internaly, may change in the future */
56    private static final int WRITE_ACCESS = 1;
57  
58    /** the size in bytes if varchar2 column in Oracle
59     *  when a String is stored in Oracle it may be too long
60     *  for a varchar2 value, and then CLOB will be used
61     *  Note that the limit is in bytes, not in characters, so
62     *  in the worst case this will limit the string to 4000/3 characters
63     *  */
64    private static final int ORACLE_VARCHAR_LIMIT_BYTES = 4000;
65  
66    /** maximum number of bytes that represent a char in UTF8 database */
67    private static final int UTF_BYTES_PER_CHAR_MAX = 3;
68  
69    /** maximum number of characters per string stored as varchar2
70     *  if longer then stored as CLOB
71     *   */
72    private static final int ORACLE_VARCHAR_MAX_SYMBOLS =
73                                    ORACLE_VARCHAR_LIMIT_BYTES/UTF_BYTES_PER_CHAR_MAX;
74  
75    /** read buffer size (for reading CLOBs) */
76    private static final int INTERNAL_BUFFER_SIZE = 16*1024;
77  
78    /** default constructor - just call the super constructor
79     *  (may change in the future)
80     *  */
81    public OracleDataStore() {
82  
83      super();
84    }
85  
86    /**
87     * Save: synchonise the in-memory image of the LR with the persistent
88     * image.
89     */
90    public String getComment() {
91      return OracleDataStore.DS_COMMENT;
92    }
93  
94    /**
95     * Returns the name of the icon to be used when this datastore is displayed
96     * in the GUI
97     */
98    public String getIconName() {
99      return OracleDataStore.DS_ICON_NAME;
100   }
101 
102 
103 
104   /** Get the name of an LR from its ID. */
105   public String getLrName(Object lrId)
106     throws PersistenceException {
107 
108     if (false == lrId instanceof Long) {
109       throw new IllegalArgumentException();
110     }
111 
112     Long ID = (Long)lrId;
113 
114     CallableStatement stmt = null;
115 
116     try {
117       stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.get_lr_name(?,?) }");
118       stmt.setLong(1,ID.longValue());
119       stmt.registerOutParameter(2,java.sql.Types.VARCHAR);
120       stmt.execute();
121       String result = stmt.getString(2);
122 
123       return result;
124     }
125     catch(SQLException sqle) {
126       throw new PersistenceException("can't get LR name from DB: ["+ sqle.getMessage()+"]");
127     }
128     finally {
129       DBHelper.cleanup(stmt);
130     }
131   }
132 
133 
134 
135   /** Set the URL for the underlying storage mechanism. */
136   public void setStorageUrl(String storageUrl) throws PersistenceException {
137 
138     super.setStorageUrl(storageUrl);
139 
140   }
141 
142 
143 
144   /** Get the URL for the underlying storage mechanism. */
145   public String getStorageUrl() {
146 
147     return super.getStorageUrl();
148   }
149 
150 
151 
152   /**
153    * Create a new data store. <B>NOTE:</B> for some data stores
154    * creation is an system administrator task; in such cases this
155    * method will throw an UnsupportedOperationException.
156    */
157   public void create()
158   throws PersistenceException, UnsupportedOperationException {
159 
160     super.create();
161   }
162 
163 
164 
165   /** Open a connection to the data store. */
166   public void open() throws PersistenceException {
167 
168     super.open();
169   }
170 
171 
172 
173   /** Close the data store. */
174   public void close() throws PersistenceException {
175 
176     super.close();
177   }
178 
179 
180 
181   /**
182    * Delete the data store. <B>NOTE:</B> for some data stores
183    * deletion is an system administrator task; in such cases this
184    * method will throw an UnsupportedOperationException.
185    */
186   public void delete()
187   throws PersistenceException, UnsupportedOperationException {
188 
189     //0. user session should be set
190 /*    if (null == this.session) {
191       throw new SecurityException("user session not set");
192     }
193 */
194     super.delete();
195   }
196 
197 
198 
199   /**
200    * Delete a resource from the data store.
201    * @param lrId a data-store specific unique identifier for the resource
202    * @param lrClassName class name of the type of resource
203    */
204   public void delete(String lrClassName, Object lrId)
205   throws PersistenceException,SecurityException {
206 
207     //0. preconditions
208     if (false == lrId instanceof Long) {
209       throw new IllegalArgumentException();
210     }
211 
212     if (!lrClassName.equals(DBHelper.DOCUMENT_CLASS) &&
213         !lrClassName.equals(DBHelper.CORPUS_CLASS)) {
214       throw new IllegalArgumentException("Only Corpus and Document classes are supported" +
215                                           " by Database data store");
216     }
217 
218     //1. check session
219     if (null == this.session) {
220       throw new SecurityException("session not set");
221     }
222 
223     if (false == this.ac.isValidSession(this.session)) {
224       throw new SecurityException("invalid session supplied");
225     }
226 
227     //2. check permissions
228     if (false == canWriteLR(lrId)) {
229       throw new SecurityException("insufficient privileges");
230     }
231 
232     //3. try to lock document, so that we'll be sure no one is editing it
233     //NOTE: use the private method
234     User lockingUser = this.getLockingUser((Long)lrId);
235     User currUser = this.session.getUser();
236 
237     if (null != lockingUser && false == lockingUser.equals(currUser)) {
238       //oops, someone is editing now
239       throw new PersistenceException("LR locked by another user");
240     }
241 
242     boolean transFailed = false;
243     try {
244       //4. autocommit should be FALSE because of LOBs
245       this.jdbcConn.setAutoCommit(false);
246 
247       //5. perform changes, if anything goes wrong, rollback
248       if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
249         deleteDocument((Long)lrId);
250       }
251       else {
252         deleteCorpus((Long)lrId);
253       }
254 
255       //6. done, commit
256       this.jdbcConn.commit();
257     }
258     catch(SQLException sqle) {
259       transFailed = true;
260       throw new PersistenceException("Cannot start/commit a transaction, ["+sqle.getMessage()+"]");
261     }
262     catch(PersistenceException pe) {
263       transFailed = true;
264       throw(pe);
265     }
266     finally {
267       //problems?
268       if (transFailed) {
269         try {
270           this.jdbcConn.rollback();
271         }
272         catch(SQLException sqle) {
273           throw new PersistenceException(sqle);
274         }
275       }
276     }
277 
278     //7, unlock
279     //do nothing - the resource does not exist anymore
280 
281     //8. delete from the list of dependent resources
282     boolean resourceFound = false;
283     Iterator it = this.dependentResources.iterator();
284     while (it.hasNext()) {
285       LanguageResource lr = (LanguageResource)it.next();
286       if (lr.getLRPersistenceId().equals(lrId)) {
287         resourceFound = true;
288         it.remove();
289         break;
290       }
291     }
292 
293     //Assert.assertTrue(resourceFound);
294 
295     //9. let the world know about it
296     fireResourceDeleted(
297       new DatastoreEvent(this, DatastoreEvent.RESOURCE_DELETED, null, lrId));
298 
299     //10. unload the resource form the GUI
300     try {
301       unloadLR((Long)lrId);
302     }
303     catch(GateException ge) {
304       Err.prln("can't unload resource from GUI...");
305     }
306   }
307 
308 
309 
310   /**
311    *  helper method for delete()
312    *  never call it directly beause proper events will not be fired
313    */
314   private void deleteDocument(Long lrId)
315   throws PersistenceException {
316 
317     //0. preconditions
318     Assert.assertNotNull(lrId);
319 
320     CallableStatement stmt = null;
321 
322     //1. delete from DB
323     try {
324       stmt = this.jdbcConn.prepareCall(
325                       "{ call "+Gate.DB_OWNER+".persist.delete_document(?) }");
326       stmt.setLong(1,lrId.longValue());
327       stmt.execute();
328     }
329     catch(SQLException sqle) {
330       throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
331     }
332     finally {
333       DBHelper.cleanup(stmt);
334     }
335   }
336 
337 
338 
339   /**
340    *  helper method for delete()
341    *  never call it directly beause proper events will not be fired
342    */
343   private void deleteCorpus(Long lrId)
344   throws PersistenceException {
345 
346     Long ID = (Long)lrId;
347 
348     CallableStatement stmt = null;
349 
350     try {
351       stmt = this.jdbcConn.prepareCall(
352                       "{ call "+Gate.DB_OWNER+".persist.delete_corpus(?) }");
353       stmt.setLong(1,ID.longValue());
354       stmt.execute();
355     }
356     catch(SQLException sqle) {
357       throw new PersistenceException("can't delete LR from DB: ["+ sqle.getMessage()+"]");
358     }
359     finally {
360       DBHelper.cleanup(stmt);
361     }
362   }
363 
364 
365 
366   /**
367    * Save: synchonise the in-memory image of the LR with the persistent
368    * image.
369    */
370   public void sync(LanguageResource lr)
371   throws PersistenceException,SecurityException {
372 
373     //4.delegate (open a new transaction)
374     _sync(lr,true);
375   }
376 
377 
378 
379   /**
380    * Save: synchonise the in-memory image of the LR with the persistent
381    * image.
382    */
383   private void _sync(LanguageResource lr, boolean openNewTrans)
384     throws PersistenceException,SecurityException {
385 
386     //0.preconditions
387     Assert.assertNotNull(lr);
388     Long lrID = (Long)lr.getLRPersistenceId();
389 
390     if (false == lr instanceof Document &&
391         false == lr instanceof Corpus) {
392       //only documents and corpuses could be serialized in DB
393       throw new IllegalArgumentException("only Documents and Corpuses could "+
394                                           "be serialized in DB");
395     }
396 
397     // check that this LR is one of ours (i.e. has been adopted)
398     if( null == lr.getDataStore() || false == lr.getDataStore().equals(this))
399       throw new PersistenceException(
400         "This LR is not stored in this DataStore"
401       );
402 
403 
404     //1. check session
405     if (null == this.session) {
406       throw new SecurityException("session not set");
407     }
408 
409     if (false == this.ac.isValidSession(this.session)) {
410       throw new SecurityException("invalid session supplied");
411     }
412 
413     //2. check permissions
414     if (false == canWriteLR(lrID)) {
415       throw new SecurityException("insufficient privileges");
416     }
417 
418     //3. is the resource locked?
419     User lockingUser = getLockingUser(lr);
420     User currUser = this.session.getUser();
421 
422     if (lockingUser != null && false == lockingUser.equals(currUser)) {
423       throw new PersistenceException("document is locked by another user and cannot be synced");
424     }
425 
426 
427     boolean transFailed = false;
428     try {
429       //2. autocommit should be FALSE because of LOBs
430       if (openNewTrans) {
431         this.jdbcConn.setAutoCommit(false);
432       }
433 
434       //3. perform changes, if anything goes wrong, rollback
435       if (lr instanceof Document) {
436         syncDocument((Document)lr);
437       }
438       else {
439         syncCorpus((Corpus)lr);
440       }
441 
442       //4. done, commit
443       if (openNewTrans) {
444         this.jdbcConn.commit();
445       }
446     }
447     catch(SQLException sqle) {
448       transFailed = true;
449       throw new PersistenceException("Cannot start/commit a transaction, ["+sqle.getMessage()+"]");
450     }
451     catch(PersistenceException pe) {
452       transFailed = true;
453       throw(pe);
454     }
455     finally {
456       //problems?
457       if (transFailed) {
458         try {
459           this.jdbcConn.rollback();
460         }
461         catch(SQLException sqle) {
462           throw new PersistenceException(sqle);
463         }
464       }
465     }
466 
467     // let the world know about it
468     fireResourceWritten(
469       new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN, lr, lr.getLRPersistenceId()));
470 
471   }
472 
473 
474 
475   /**
476    * Set method for the autosaving behaviour of the data store.
477    * <B>NOTE:</B> many types of datastore have no auto-save function,
478    * in which case this will throw an UnsupportedOperationException.
479    */
480   public void setAutoSaving(boolean autoSaving)
481   throws UnsupportedOperationException,PersistenceException {
482 
483     super.setAutoSaving(autoSaving);
484   }
485 
486 
487 
488   /** Get the autosaving behaviour of the LR. */
489   public boolean isAutoSaving() {
490     throw new MethodNotImplementedException();
491   }
492 
493 
494   /** Adopt a resource for persistence. */
495   public LanguageResource adopt(LanguageResource lr, SecurityInfo secInfo)
496   throws PersistenceException,SecurityException {
497     //open a new transaction
498     return _adopt(lr,secInfo,true);
499   }
500 
501   /** helper for adopt()
502    *  @param openNewTrans shows if a new transaction should be started or the adopt
503    *  is performed in the context of an already opened transaction
504    */
505   private LanguageResource _adopt(LanguageResource lr,
506                                   SecurityInfo secInfo,
507                                   boolean openNewTrans)
508   throws PersistenceException,SecurityException {
509 
510     LanguageResource result = null;
511 
512     //-1. preconditions
513     Assert.assertNotNull(lr);
514     Assert.assertNotNull(secInfo);
515     if (false == lr instanceof Document &&
516         false == lr instanceof Corpus) {
517       //only documents and corpuses could be serialized in DB
518       throw new IllegalArgumentException("only Documents and Corpuses could "+
519                                           "be serialized in DB");
520     }
521 
522     //0. check SecurityInfo
523     if (false == this.ac.isValidSecurityInfo(secInfo)) {
524       throw new SecurityException("Invalid security settings supplied");
525     }
526 
527     //1. user session should be set
528     if (null == this.session) {
529       throw new SecurityException("user session not set");
530     }
531 
532     //2. check the LR's current DS
533     DataStore currentDS = lr.getDataStore();
534     if(currentDS == null) {  // an orphan - do the adoption
535       //do not set the datastore on the lr, because actually it should
536       //remain transient. The DS should only be set on the one that is
537       //saved in Oracle
538 //      lr.setDataStore(this);
539       // let the world know
540       //fireResourceAdopted(
541       //    new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED, lr, null)
542       //);
543     }
544     else if(currentDS.equals(this)){         // adopted already
545       return lr;
546     }
547     else {                      // someone else's child
548       throw new PersistenceException(
549         "Can't adopt a resource which is already in a different datastore");
550     }
551 
552 
553     //3. is the LR one of Document or Corpus?
554     if (false == lr instanceof Document &&
555         false == lr instanceof Corpus) {
556 
557       throw new IllegalArgumentException("Database datastore is implemented only for "+
558                                         "Documents and Corpora");
559     }
560 
561     //4.is the document already stored in this storage?
562     Object persistID = lr.getLRPersistenceId();
563     if (persistID != null) {
564       throw new PersistenceException("This LR is already stored in the " +
565                                       " database (persistance ID is =["+(Long)persistID+"] )");
566     }
567 
568     boolean transFailed = false;
569     try {
570       //5 autocommit should be FALSE because of LOBs
571       if (openNewTrans) {
572         this.jdbcConn.setAutoCommit(false);
573       }
574 
575       //6. perform changes, if anything goes wrong, rollback
576       if (lr instanceof Document) {
577         result =  createDocument((Document)lr,secInfo);
578 //System.out.println("result ID=["+result.getLRPersistenceId()+"]");
579       }
580       else {
581         result =  createCorpus((Corpus)lr,secInfo);
582       }
583 
584       //7. done, commit
585       if (openNewTrans) {
586         this.jdbcConn.commit();
587       }
588     }
589     catch(SQLException sqle) {
590       transFailed = true;
591       throw new PersistenceException("Cannot start/commit a transaction, ["+sqle.getMessage()+"]");
592     }
593     catch(PersistenceException pe) {
594       transFailed = true;
595       throw(pe);
596     }
597     catch(SecurityException se) {
598       transFailed = true;
599       throw(se);
600     }
601     finally {
602       //problems?
603       if (transFailed) {
604         try {
605           this.jdbcConn.rollback();
606         }
607         catch(SQLException sqle) {
608           throw new PersistenceException(sqle);
609         }
610       }
611     }
612 
613     //8. let the world know
614     fireResourceAdopted(
615         new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
616                            result,
617                            result.getLRPersistenceId())
618     );
619 
620     //9. fire also resource written event because it's now saved
621     fireResourceWritten(
622       new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
623                           result,
624                           result.getLRPersistenceId()
625       )
626     );
627 
628     //10. add the resource to the list of dependent resources - i.e. the ones that the
629     //data store should take care upon closing [and call sync()]
630     this.dependentResources.add(result);
631 
632     return result;
633   }
634 
635 
636 
637   /**
638    *  helper for adopt()
639    *  never call directly
640    */
641   private Long createLR(String lrType,
642                         String lrName,
643                         SecurityInfo si,
644                         Long lrParentID)
645   throws PersistenceException,SecurityException {
646 
647     //0. preconditions
648     Assert.assertNotNull(lrName);
649 
650     //1. check the session
651 //    if (this.ac.isValidSession(s) == false) {
652 //      throw new SecurityException("invalid session provided");
653 //    }
654 
655     //2. create a record in DB
656     CallableStatement stmt = null;
657 
658     try {
659       stmt = this.jdbcConn.prepareCall(
660                     "{ call "+Gate.DB_OWNER+".persist.create_lr(?,?,?,?,?,?,?) }");
661       stmt.setLong(1,si.getUser().getID().longValue());
662       stmt.setLong(2,si.getGroup().getID().longValue());
663       stmt.setString(3,lrType);
664       stmt.setString(4,lrName);
665       stmt.setInt(5,si.getAccessMode());
666       if (null == lrParentID) {
667         stmt.setNull(6,java.sql.Types.BIGINT);
668       }
669       else {
670         stmt.setLong(6,lrParentID.longValue());
671       }
672       //Oracle numbers are BIGNINT
673       stmt.registerOutParameter(7,java.sql.Types.BIGINT);
674       stmt.execute();
675 
676       Long result =  new Long(stmt.getLong(7));
677       return result;
678     }
679     catch(SQLException sqle) {
680 
681       switch(sqle.getErrorCode()) {
682         case DBHelper.X_ORACLE_INVALID_LR_TYPE:
683           throw new PersistenceException("can't create LR [step 3] in DB, invalid LR Type");
684         default:
685           throw new PersistenceException(
686                 "can't create LR [step 3] in DB : ["+ sqle.getMessage()+"]");
687       }
688     }
689     finally {
690       DBHelper.cleanup(stmt);
691     }
692   }
693 
694 
695 
696   /**
697    *  updates the content of the document if it is binary or a long string
698    *  (that does not fit into VARCHAR2)
699    */
700   private void updateDocumentContent(Long docContentID,DocumentContent content)
701   throws PersistenceException {
702 
703     //1. get LOB locators from DB
704     PreparedStatement pstmt = null;
705     ResultSet rs = null;
706     CallableStatement cstmt = null;
707     try {
708       String sql =  "select dc_content_type, " +
709                     "       dc_character_content, " +
710                     "       dc_binary_content " +
711                     "from "+gate.Gate.DB_OWNER+".t_doc_content " +
712                     "where  dc_id = ? " +
713                     "for update ";
714       pstmt = this.jdbcConn.prepareStatement(sql);
715       pstmt.setLong(1,docContentID.longValue());
716       rs = pstmt.executeQuery();
717 
718       //rs = pstmt.getResultSet();
719 
720       rs.next();
721       //important: read the objects in the order they appear in
722       //the ResultSet, otherwise data may be lost
723       long contentType = rs.getLong("DC_CONTENT_TYPE");
724       Clob clob = (Clob)rs.getClob("dc_character_content");
725       Blob blob = (Blob)rs.getBlob("dc_binary_content");
726 
727       Assert.assertTrue(contentType == DBHelper.CHARACTER_CONTENT ||
728                     contentType == DBHelper.BINARY_CONTENT ||
729                     contentType == DBHelper.EMPTY_CONTENT);
730 
731 
732       //2. write data using the LOB locators
733       //NOTE: so far only character content is supported
734       writeCLOB(content.toString(),clob);
735       long newContentType = DBHelper.CHARACTER_CONTENT;
736 
737       //3. update content type
738       cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.change_content_type(?,?) }");
739       cstmt.setLong(1,docContentID.longValue());
740       cstmt.setLong(2,newContentType);
741       cstmt.execute();
742     }
743     catch(IOException ioe) {
744       throw new PersistenceException("can't update document content in DB : ["+
745                                       ioe.getMessage()+"]");
746     }
747     catch(SQLException sqle) {
748       throw new PersistenceException("can't update document content in DB : ["+
749                                       sqle.getMessage()+"]");
750     }
751     finally {
752       DBHelper.cleanup(rs);
753       DBHelper.cleanup(pstmt);
754       DBHelper.cleanup(cstmt);
755     }
756 
757   }
758 
759 
760 
761   /**
762    * helper for adopt
763    * creates a LR of type Document
764    */
765   private Document createDocument(Document doc,SecurityInfo secInfo)
766   throws PersistenceException,SecurityException {
767 
768     //delegate, set to Null
769     return createDocument(doc,null,secInfo);
770   }
771 
772 
773 
774   /**
775    * helper for adopt
776    * creates a LR of type Document
777    */
778   private Document createDocument(Document doc, Long corpusID,SecurityInfo secInfo)
779   throws PersistenceException,SecurityException {
780 
781     //-1. preconditions
782     Assert.assertNotNull(doc);
783     Assert.assertNotNull(secInfo);
784 
785     //0. check securoity settings
786     if (false == this.ac.isValidSecurityInfo(secInfo)) {
787       throw new SecurityException("Invalid security settings");
788     }
789 
790     //1. get the data to be stored
791     AnnotationSet defaultAnnotations = doc.getAnnotations();
792     DocumentContent docContent = doc.getContent();
793     FeatureMap docFeatures = doc.getFeatures();
794     String docName  = doc.getName();
795     URL docURL = doc.getSourceUrl();
796     Boolean docIsMarkupAware = doc.getMarkupAware();
797     Long docStartOffset = doc.getSourceUrlStartOffset();
798     Long docEndOffset = doc.getSourceUrlEndOffset();
799     String docEncoding = null;
800     try {
801       docEncoding = (String)doc.getParameterValue("encoding");
802     }
803     catch(gate.creole.ResourceInstantiationException re) {
804       throw new PersistenceException("cannot create document: error getting " +
805                                      " document encoding ["+re.getMessage()+"]");
806     }
807 
808 
809     //3. create a Language Resource (an entry in T_LANG_RESOURCE) for this document
810     Long lrID = createLR(DBHelper.DOCUMENT_CLASS,docName,secInfo,null);
811 
812     //4. create a record in T_DOCUMENT for this document
813     CallableStatement stmt = null;
814     Long docID = null;
815     Long docContentID = null;
816 
817     try {
818       stmt = this.jdbcConn.prepareCall(
819           "{ call "+Gate.DB_OWNER+".persist.create_document(?,?,?,?,?,?,?,?,?) }");
820       stmt.setLong(1,lrID.longValue());
821       stmt.setString(2,docURL.toString());
822       //do we have doc encoding?
823       if (null == docEncoding) {
824         stmt.setNull(3,java.sql.Types.VARCHAR);
825       }
826       else {
827         stmt.setString(3,docEncoding);
828       }
829       //do we have start offset?
830       if (null==docStartOffset) {
831         stmt.setNull(4,java.sql.Types.NUMERIC);
832       }
833       else {
834         stmt.setLong(4,docStartOffset.longValue());
835       }
836       //do we have end offset?
837       if (null==docEndOffset) {
838         stmt.setNull(5,java.sql.Types.NUMERIC);
839       }
840       else {
841         stmt.setLong(5,docEndOffset.longValue());
842       }
843 
844       stmt.setBoolean(6,docIsMarkupAware.booleanValue());
845 
846       //is the document part of a corpus?
847       if (null == corpusID) {
848         stmt.setNull(7,java.sql.Types.BIGINT);
849       }
850       else {
851         stmt.setLong(7,corpusID.longValue());
852       }
853 
854       //results
855       stmt.registerOutParameter(8,java.sql.Types.BIGINT);
856       stmt.registerOutParameter(9,java.sql.Types.BIGINT);
857 
858       stmt.execute();
859       docID = new Long(stmt.getLong(8));
860       docContentID = new Long(stmt.getLong(9));
861     }
862     catch(SQLException sqle) {
863       throw new PersistenceException("can't create document [step 4] in DB: ["+ sqle.getMessage()+"]");
864     }
865     finally {
866       DBHelper.cleanup(stmt);
867     }
868 
869     //5. fill document content (record[s] in T_DOC_CONTENT)
870 
871     //do we have content at all?
872     if (docContent.size().longValue() > 0) {
873       updateDocumentContent(docContentID,docContent);
874     }
875 
876     //6. insert annotations, etc
877 
878     //6.1. create default annotation set
879     createAnnotationSet(lrID,defaultAnnotations);
880 
881     //6.2. create named annotation sets
882     Map namedAnns = doc.getNamedAnnotationSets();
883     //the map may be null
884     if (null != namedAnns) {
885       Set setAnns = namedAnns.entrySet();
886       Iterator itAnns = setAnns.iterator();
887 
888       while (itAnns.hasNext()) {
889         Map.Entry mapEntry = (Map.Entry)itAnns.next();
890         //String currAnnName = (String)mapEntry.getKey();
891         AnnotationSet currAnnSet = (AnnotationSet)mapEntry.getValue();
892 
893         //create a-sets
894         createAnnotationSet(lrID,currAnnSet);
895       }
896     }
897 
898     //7. create features
899     createFeatures(lrID,DBHelper.FEATURE_OWNER_DOCUMENT,docFeatures);
900 
901     //9. create a DatabaseDocument wrapper and return it
902 
903     Document dbDoc = new DatabaseDocumentImpl(this.jdbcConn,
904                                               doc.getName(),
905                                               this,
906                                               lrID,
907                                               doc.getContent(),
908                                               doc.getFeatures(),
909                                               doc.getMarkupAware(),
910                                               doc.getSourceUrl(),
911                                               doc.getSourceUrlStartOffset(),
912                                               doc.getSourceUrlEndOffset(),
913                                               doc.getAnnotations(),
914                                               doc.getNamedAnnotationSets());
915 
916     return dbDoc;
917   }
918 
919 
920 
921   /** creates an entry for annotation set in the database */
922   private void createAnnotationSet(Long lrID, AnnotationSet aset)
923     throws PersistenceException {
924 
925     //1. create a-set
926     String asetName = aset.getName();
927     Long asetID = null;
928 
929     //DB stuff
930     CallableStatement stmt = null;
931       try {
932         stmt = this.jdbcConn.prepareCall(
933             "{ call "+Gate.DB_OWNER+".persist.create_annotation_set(?,?,?) }");
934         stmt.setLong(1,lrID.longValue());
935 
936         if (null == asetName) {
937           stmt.setNull(2,java.sql.Types.VARCHAR);
938         }
939         else {
940           stmt.setString(2,asetName);
941         }
942         stmt.registerOutParameter(3,java.sql.Types.BIGINT);
943         stmt.execute();
944 
945         asetID = new Long(stmt.getLong(3));
946       }
947       catch(SQLException sqle) {
948         throw new PersistenceException("can't create a-set [step 1] in DB: ["+ sqle.getMessage()+"]");
949       }
950       finally {
951         DBHelper.cleanup(stmt);
952       }
953 
954 
955     //2. insert annotations/nodes for DEFAULT a-set
956     //for now use a stupid cycle
957     //TODO: pass all the data with one DB call (?)
958     Iterator itAnnotations = aset.iterator();
959     while (itAnnotations.hasNext()) {
960       Annotation ann = (Annotation)itAnnotations.next();
961       Node start = (Node)ann.getStartNode();
962       Node end = (Node)ann.getEndNode();
963       String type = ann.getType();
964 
965       //DB stuff
966       Long annGlobalID = null;
967       try {
968         stmt = this.jdbcConn.prepareCall(
969             "{ call "+Gate.DB_OWNER+".persist.create_annotation(?,?,?,?,?,?,?,?,?) }");
970         stmt.setLong(1,lrID.longValue());
971         stmt.setLong(2,ann.getId().longValue());
972         stmt.setLong(3,asetID.longValue());
973         stmt.setLong(4,start.getId().longValue());
974         stmt.setLong(5,start.getOffset().longValue());
975         stmt.setLong(6,end.getId().longValue());
976         stmt.setLong(7,end.getOffset().longValue());
977         stmt.setString(8,type);
978         stmt.registerOutParameter(9,java.sql.Types.BIGINT);
979 
980         stmt.execute();
981 
982         annGlobalID = new Long(stmt.getLong(9));
983       }
984       catch(SQLException sqle) {
985         switch(sqle.getErrorCode()) {
986           case DBHelper.X_ORACLE_INVALID_ANNOTATION_TYPE:
987             throw new PersistenceException(
988                 "can't create annotation in DB, [invalid annotation type]");
989           default:
990             throw new PersistenceException(
991                 "can't create annotation in DB: ["+ sqle.getMessage()+"]");
992       }
993 
994       }
995       finally {
996         DBHelper.cleanup(stmt);
997       }
998 
999       //2.1. set annotation features
1000      FeatureMap features = ann.getFeatures();
1001      Assert.assertNotNull(features);
1002      createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
1003    }
1004  }
1005
1006
1007
1008  /** creates a LR of type Corpus  */
1009  private Corpus createCorpus(Corpus corp,SecurityInfo secInfo)
1010    throws PersistenceException,SecurityException {
1011
1012    //1. create an LR entry for the corpus (T_LANG_RESOURCE table)
1013    Long lrID = createLR(DBHelper.CORPUS_CLASS,corp.getName(),secInfo,null);
1014
1015    //2.create am entry in the T_COPRUS table
1016    Long corpusID = null;
1017    //DB stuff
1018    CallableStatement stmt = null;
1019      try {
1020        stmt = this.jdbcConn.prepareCall(
1021            "{ call "+Gate.DB_OWNER+".persist.create_corpus(?,?) }");
1022        stmt.setLong(1,lrID.longValue());
1023        stmt.registerOutParameter(2,java.sql.Types.BIGINT);
1024        stmt.execute();
1025        corpusID = new Long(stmt.getLong(2));
1026      }
1027      catch(SQLException sqle) {
1028        throw new PersistenceException("can't create corpus [step 2] in DB: ["+ sqle.getMessage()+"]");
1029      }
1030      finally {
1031        DBHelper.cleanup(stmt);
1032      }
1033
1034    //3. for each document in the corpus call createDocument()
1035    Iterator itDocuments = corp.iterator();
1036    Vector dbDocs = new Vector();
1037    while (itDocuments.hasNext()) {
1038      Document doc = (Document)itDocuments.next();
1039
1040      //3.1. ensure that the document is either transient or is from the ...
1041      // same DataStore
1042      if (doc.getLRPersistenceId() == null) {
1043        //transient document
1044        Document dbDoc = createDocument(doc,corpusID,secInfo);
1045        dbDocs.add(dbDoc);
1046      }
1047      else if (doc.getDataStore().equals(this)) {
1048        //persistent doc from the same DataStore
1049        fireResourceAdopted(
1050            new DatastoreEvent(this, DatastoreEvent.RESOURCE_ADOPTED,
1051                               doc,
1052                               doc.getLRPersistenceId()));
1053
1054        //6. fire also resource written event because it's now saved
1055        fireResourceWritten(
1056          new DatastoreEvent(this, DatastoreEvent.RESOURCE_WRITTEN,
1057                              doc,
1058                              doc.getLRPersistenceId()));
1059      }
1060      else {
1061        //persistent doc from other datastore
1062        //skip
1063        gate.util.Err.prln("document ["+doc.getLRPersistenceId()+"] is adopted from another "+
1064                            " datastore. Skipped.");
1065      }
1066    }
1067
1068    //4. create features
1069    createFeatures(lrID,DBHelper.FEATURE_OWNER_CORPUS,corp.getFeatures());
1070
1071    //5. create a DatabaseCorpusImpl and return it
1072    Corpus dbCorpus = new DatabaseCorpusImpl(corp.getName(),
1073                                             this,
1074                                              lrID,
1075                                              corp.getFeatures(),
1076                                              dbDocs);
1077
1078    //6. done
1079    return dbCorpus;
1080  }
1081
1082
1083
1084  /**
1085   * Get a resource from the persistent store.
1086   * <B>Don't use this method - use Factory.createResource with
1087   * DataStore and DataStoreInstanceId parameters set instead.</B>
1088   */
1089  public LanguageResource getLr(String lrClassName, Object lrPersistenceId)
1090  throws PersistenceException,SecurityException {
1091
1092    LanguageResource result = null;
1093
1094    //0. preconditions
1095    Assert.assertNotNull(lrPersistenceId);
1096
1097    //1. check session
1098    if (null == this.session) {
1099      throw new SecurityException("session not set");
1100    }
1101
1102    if (false == this.ac.isValidSession(this.session)) {
1103      throw new SecurityException("invalid session supplied");
1104    }
1105
1106    //2. check permissions
1107    if (false == canReadLR(lrPersistenceId)) {
1108      throw new SecurityException("insufficient privileges");
1109    }
1110
1111    //3. get resource from DB
1112    if (lrClassName.equals(DBHelper.DOCUMENT_CLASS)) {
1113      result = readDocument(lrPersistenceId);
1114      Assert.assertTrue(result instanceof DatabaseDocumentImpl);
1115    }
1116    else if (lrClassName.equals(DBHelper.CORPUS_CLASS)) {
1117      result = readCorpus(lrPersistenceId);
1118      Assert.assertTrue(result instanceof DatabaseCorpusImpl);
1119    }
1120    else {
1121      throw new IllegalArgumentException("resource class should be either Document or Corpus");
1122    }
1123
1124    //4. postconditions
1125    Assert.assertNotNull(result.getDataStore());
1126    Assert.assertTrue(result.getDataStore() instanceof DatabaseDataStore);
1127    Assert.assertNotNull(result.getLRPersistenceId());
1128
1129    //5. register the read doc as listener for sync events
1130    addDatastoreListener((DatastoreListener)result);
1131
1132    //6. add the resource to the list of dependent resources - i.e. the ones that the
1133    //data store should take care upon closing [and call sync()]
1134    this.dependentResources.add(result);
1135
1136    //7. done
1137    return result;
1138  }
1139
1140
1141
1142  /** Get a list of the types of LR that are present in the data store. */
1143  public List getLrTypes() throws PersistenceException {
1144
1145    Vector lrTypes = new Vector();
1146    Statement stmt = null;
1147    ResultSet rs = null;
1148
1149    try {
1150      stmt = this.jdbcConn.createStatement();
1151      rs = stmt.executeQuery(" SELECT lrtp_type " +
1152                             " FROM   "+Gate.DB_OWNER+".t_lr_type LRTYPE ");
1153
1154      while (rs.next()) {
1155        //access by index is faster
1156        String lrType = rs.getString(1);
1157        lrTypes.add(lrType);
1158      }
1159
1160      return lrTypes;
1161    }
1162    catch(SQLException sqle) {
1163      throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
1164    }
1165    finally {
1166      DBHelper.cleanup(rs);
1167      DBHelper.cleanup(stmt);
1168    }
1169  }
1170
1171
1172
1173  /** Get a list of the IDs of LRs of a particular type that are present. */
1174  public List getLrIds(String lrType) throws PersistenceException {
1175
1176    Vector lrIDs = new Vector();
1177    PreparedStatement stmt = null;
1178    ResultSet rs = null;
1179
1180    try {
1181      stmt = this.jdbcConn.prepareStatement(
1182                      " SELECT lr_id " +
1183                      " FROM   "+Gate.DB_OWNER+".t_lang_resource LR, " +
1184                      "        "+Gate.DB_OWNER+".t_lr_type LRTYPE " +
1185                      " WHERE  LR.lr_type_id = LRTYPE.lrtp_id " +
1186                      "        AND LRTYPE.lrtp_type = ? " +
1187                      " ORDER BY lr_name"
1188                      );
1189      stmt.setString(1,lrType);
1190      stmt.execute();
1191      rs = stmt.getResultSet();
1192
1193      while (rs.next()) {
1194        //access by index is faster
1195        Long lrID = new Long(rs.getLong(1));
1196        lrIDs.add(lrID);
1197      }
1198
1199      return lrIDs;
1200    }
1201    catch(SQLException sqle) {
1202      throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
1203    }
1204    finally {
1205      DBHelper.cleanup(rs);
1206      DBHelper.cleanup(stmt);
1207    }
1208
1209  }
1210
1211
1212
1213  /** Get a list of the names of LRs of a particular type that are present. */
1214  public List getLrNames(String lrType) throws PersistenceException {
1215
1216    Vector lrNames = new Vector();
1217    PreparedStatement stmt = null;
1218    ResultSet rs = null;
1219
1220    try {
1221      stmt = this.jdbcConn.prepareStatement(
1222                " SELECT lr_name " +
1223                " FROM   "+Gate.DB_OWNER+".t_lang_resource LR, " +
1224                "        t_lr_type LRTYPE " +
1225                " WHERE  LR.lr_type_id = LRTYPE.lrtp_id " +
1226                "        AND LRTYPE.lrtp_type = ? "
1227                );
1228      stmt.setString(1,lrType);
1229      stmt.execute();
1230      rs = stmt.getResultSet();
1231
1232      while (rs.next()) {
1233        //access by index is faster
1234        String lrName = rs.getString(1);
1235        lrNames.add(lrName);
1236      }
1237
1238      return lrNames;
1239    }
1240    catch(SQLException sqle) {
1241      throw new PersistenceException("can't get LR types from DB: ["+ sqle.getMessage()+"]");
1242    }
1243    finally {
1244      DBHelper.cleanup(rs);
1245      DBHelper.cleanup(stmt);
1246    }
1247
1248  }
1249
1250
1251
1252  /** Gets a timestamp marker that will be used for all changes made in
1253   *  the database so that subsequent calls to deleteSince() could restore (partly)
1254   *  the database state as it was before the update. <B>NOTE:</B> Restoring the previous
1255   *  state may not be possible at all (i.e. if DELETE is performed)
1256   *   */
1257  public Long timestamp()
1258    throws PersistenceException{
1259
1260    CallableStatement stmt = null;
1261
1262    try {
1263      stmt = this.jdbcConn.prepareCall(
1264                "{ call "+Gate.DB_OWNER+".persist.get_timestamp(?)} ");
1265      //numbers generated from Oracle sequences are BIGINT
1266      stmt.registerOutParameter(1,java.sql.Types.BIGINT);
1267      stmt.execute();
1268      long result = stmt.getLong(1);
1269
1270      return new Long(result);
1271    }
1272    catch(SQLException sqle) {
1273      throw new PersistenceException("can't get a timestamp from DB: ["+ sqle.getMessage()+"]");
1274    }
1275
1276  }
1277
1278
1279
1280  /**
1281   * Checks if the user (identified by the sessionID)
1282   *  has read access to the LR
1283   */
1284  public boolean canReadLR(Object lrID)
1285    throws PersistenceException, SecurityException{
1286
1287    return canAccessLR((Long) lrID,READ_ACCESS);
1288  }
1289
1290
1291
1292  /**
1293   * Checks if the user (identified by the sessionID)
1294   * has write access to the LR
1295   */
1296  public boolean canWriteLR(Object lrID)
1297    throws PersistenceException, SecurityException{
1298
1299    return canAccessLR((Long) lrID,WRITE_ACCESS);
1300  }
1301
1302
1303
1304  /**
1305   * Checks if the user (identified by the sessionID)
1306   * has some access (read/write) to the LR
1307   */
1308  private boolean canAccessLR(Long lrID,int mode)
1309    throws PersistenceException, SecurityException{
1310
1311    //0. preconditions
1312    Assert.assertTrue(READ_ACCESS == mode || WRITE_ACCESS == mode);
1313
1314    //1. is session initialised?
1315    if (null == this.session) {
1316      throw new SecurityException("user session not set");
1317    }
1318
1319    //2.first check the session and then check whether the user is member of the group
1320    if (this.ac.isValidSession(this.session) == false) {
1321      throw new SecurityException("invalid session supplied");
1322    }
1323
1324    CallableStatement stmt = null;
1325
1326    try {
1327      stmt = this.jdbcConn.prepareCall(
1328                "{ call "+Gate.DB_OWNER+".security.has_access_to_lr(?,?,?,?,?)} ");
1329      stmt.setLong(1,lrID.longValue());
1330      stmt.setLong(2,this.session.getUser().getID().longValue());
1331      stmt.setLong(3,this.session.getGroup().getID().longValue());
1332      stmt.setLong(4,mode);
1333
1334      stmt.registerOutParameter(5,java.sql.Types.NUMERIC);
1335      stmt.execute();
1336      int result = stmt.getInt(5);
1337
1338      return (ORACLE_TRUE == result);
1339    }
1340    catch(SQLException sqle) {
1341      throw new PersistenceException("can't check permissions in DB: ["+ sqle.getMessage()+"]");
1342    }
1343    finally {
1344      DBHelper.cleanup(stmt);
1345    }
1346  }
1347
1348
1349
1350  /** reads the content of a CLOB into the specified StringBuffer */
1351  public static void readCLOB(java.sql.Clob src, StringBuffer dest)
1352    throws SQLException, IOException {
1353
1354    int readLength = 0;
1355
1356    //1. empty the dest buffer
1357    dest.delete(0,dest.length());
1358
1359    //2. get Oracle CLOB
1360    CLOB clo = (CLOB)src;
1361
1362    //3. create temp buffer
1363    int buffSize = Math.max(INTERNAL_BUFFER_SIZE,clo.getBufferSize());
1364    char[] readBuffer = new char[buffSize];
1365
1366    //3. get Unicode stream
1367    Reader input = clo.getCharacterStream();
1368
1369    //4. read
1370    BufferedReader buffInput = new BufferedReader(input,INTERNAL_BUFFER_SIZE);
1371
1372    while ((readLength = buffInput.read(readBuffer, 0, INTERNAL_BUFFER_SIZE)) != -1) {
1373      dest.append(readBuffer, 0, readLength);
1374    }
1375
1376    //5.close streams
1377    buffInput.close();
1378    input.close();
1379
1380  }
1381
1382
1383
1384  /** writes the content of a String into the specified CLOB object */
1385  public static void writeCLOB(String src,java.sql.Clob dest)
1386    throws SQLException, IOException {
1387
1388    //preconditions
1389    Assert.assertNotNull(src);
1390
1391    //1. get Oracle CLOB
1392    CLOB clo = (CLOB)dest;
1393
1394    //2. get Unicode stream
1395    Writer output = clo.getCharacterOutputStream();
1396
1397    //3. write
1398    BufferedWriter buffOutput = new BufferedWriter(output,INTERNAL_BUFFER_SIZE);
1399    buffOutput.write(src.toString());
1400
1401    //4. flushing is a good idea [although BufferedWriter::close() calls it this is
1402    //implementation specific]
1403    buffOutput.flush();
1404    output.flush();
1405
1406    //5.close streams
1407    buffOutput.close();
1408    output.close();
1409  }
1410
1411
1412
1413  /** writes the content of a StringBuffer into the specified CLOB object */
1414  public static void writeCLOB(StringBuffer src,java.sql.Clob dest)
1415    throws SQLException, IOException {
1416
1417    //delegate
1418    writeCLOB(src.toString(),dest);
1419  }
1420
1421
1422
1423  /**
1424   *  reads the content of the specified BLOB object and returns the object
1425   *  contained.
1426   *  NOTE: the BLOB is expected to contain serializable objects, not just any
1427   *  binary stream
1428   */
1429  public static Object readBLOB(java.sql.Blob src)
1430    throws SQLException, IOException,ClassNotFoundException {
1431
1432    int readLength = 0;
1433    Object result = null;
1434
1435    //0. preconditions
1436    Assert.assertNotNull(src);
1437
1438    //2. get Oracle BLOB
1439    BLOB blo = (BLOB)src;
1440
1441    //3. get binary stream
1442    InputStream input = blo.getBinaryStream();
1443    Assert.assertNotNull(input);
1444
1445    //4. read
1446    ObjectInputStream ois = new ObjectInputStream(input);
1447    result = ois.readObject();
1448
1449    //5.close streams
1450    ois.close();
1451    input.close();
1452
1453    return result;
1454  }
1455
1456
1457
1458  /**
1459   *  writes the specified object into the BLOB
1460   *  NOTE: the object should be serializable
1461   */
1462  public static void writeBLOB(Object src,java.sql.Blob dest)
1463    throws SQLException, IOException {
1464
1465    //preconditions
1466    Assert.assertNotNull(src);
1467
1468    //1. get Oracle CLOB
1469    BLOB blo = (BLOB)dest;
1470
1471    //2. get Unicode stream
1472    OutputStream output = blo.getBinaryOutputStream();
1473
1474    //3. write
1475    ObjectOutputStream oos = new ObjectOutputStream(output);
1476    oos.writeObject(src);
1477
1478    //4. flushing is a good idea
1479    //[although ::close() calls it this is implementation specific]
1480    oos.flush();
1481    output.flush();
1482
1483    //5.close streams
1484    oos.close();
1485    output.close();
1486  }
1487
1488
1489
1490  /**
1491   *  creates a feature of the specified type/value/valueType/key for the specified entity
1492   *  Entity is one of: LR, Annotation
1493   *  Value types are: boolean, int, long, string, float, Object
1494   */
1495  private Long _createFeature(Long entityID,
1496                              int entityType,
1497                              String key,
1498                              Object value,
1499                              int valueType)
1500    throws PersistenceException {
1501
1502    //1. store in DB
1503    Long featID = null;
1504    CallableStatement stmt = null;
1505
1506    try {
1507      stmt = this.jdbcConn.prepareCall(
1508                "{ call "+Gate.DB_OWNER+".persist.create_feature(?,?,?,?,?,?,?)} ");
1509
1510      //1.1 set known values + NULLs
1511      stmt.setLong(1,entityID.longValue());
1512      stmt.setLong(2,entityType);
1513      stmt.setString(3,key);
1514      stmt.setNull(4,java.sql.Types.NUMERIC);
1515      stmt.setNull(5,java.sql.Types.VARCHAR);
1516      stmt.setLong(6,valueType);
1517      stmt.registerOutParameter(7,java.sql.Types.BIGINT);
1518
1519      //1.2 set proper data
1520      switch(valueType) {
1521
1522        case DBHelper.VALUE_TYPE_NULL:
1523          break;
1524
1525        case DBHelper.VALUE_TYPE_BOOLEAN:
1526
1527          boolean b = ((Boolean)value).booleanValue();
1528          stmt.setLong(4, b ? this.ORACLE_TRUE : this.ORACLE_FALSE);
1529          break;
1530
1531        case DBHelper.VALUE_TYPE_INTEGER:
1532
1533          stmt.setLong(4,((Integer)value).intValue());
1534          break;
1535
1536        case DBHelper.VALUE_TYPE_LONG:
1537
1538          stmt.setLong(4,((Long)value).longValue());
1539          break;
1540
1541        case DBHelper.VALUE_TYPE_FLOAT:
1542
1543          Double d = (Double)value;
1544          stmt.setDouble(4,d.doubleValue());
1545          break;
1546
1547        case DBHelper.VALUE_TYPE_BINARY:
1548          //ignore
1549          //will be handled later in processing
1550          break;
1551
1552        case DBHelper.VALUE_TYPE_STRING:
1553
1554          String s = (String)value;
1555          //does it fin into a varchar2?
1556          if (fitsInVarchar2(s)) {
1557            stmt.setString(5,s);
1558          }
1559          break;
1560
1561        default:
1562          throw new IllegalArgumentException("unsuppoeted feature type");
1563      }
1564
1565      stmt.execute();
1566      featID = new Long(stmt.getLong(7));
1567    }
1568    catch(SQLException sqle) {
1569
1570      switch(sqle.getErrorCode()) {
1571        case DBHelper.X_ORACLE_INVALID_FEATURE_TYPE:
1572          throw new PersistenceException("can't create feature [step 1],"+
1573                      "[invalid feature type] in DB: ["+ sqle.getMessage()+"]");
1574        default:
1575          throw new PersistenceException("can't create feature [step 1] in DB: ["+
1576                                                      sqle.getMessage()+"]");
1577      }
1578    }
1579    finally {
1580      DBHelper.cleanup(stmt);
1581    }
1582
1583    return featID;
1584  }
1585
1586
1587
1588  /**
1589   *  updates the value of a feature where the value is string (>4000 bytes, stored as CLOB)
1590   *  or Object (stored as BLOB)
1591   */
1592  private void _updateFeatureLOB(Long featID,Object value, int valueType)
1593    throws PersistenceException {
1594
1595    //NOTE: at this point value is never an array,
1596    // although the type may claim so
1597
1598    //0. preconditions
1599    Assert.assertTrue(valueType == DBHelper.VALUE_TYPE_BINARY ||
1600                  valueType == DBHelper.VALUE_TYPE_STRING);
1601
1602
1603    //1. get the row to be updated
1604    PreparedStatement stmtA = null;
1605    ResultSet rsA = null;
1606    Clob clobValue = null;
1607    Blob blobValue = null;
1608
1609    try {
1610      String sql = " select ft_long_character_value, " +
1611                   "        ft_binary_value " +
1612                   " from  "+Gate.DB_OWNER+".t_feature " +
1613                   " where  ft_id = ? ";
1614
1615      stmtA = this.jdbcConn.prepareStatement(sql);
1616      stmtA.setLong(1,featID.longValue());
1617      stmtA.execute();
1618      rsA = stmtA.getResultSet();
1619
1620      if (false == rsA.next()) {
1621        throw new PersistenceException("Incorrect feature ID supplied ["+featID+"]");
1622      }
1623
1624      //NOTE1: if the result set contains LOBs always read them
1625      // in the order they appear in the SQL query
1626      // otherwise data will be lost
1627      //NOTE2: access by index rather than name is usually faster
1628      clobValue = rsA.getClob(1);
1629      blobValue = rsA.getBlob(2);
1630
1631      //blob or clob?
1632      if (valueType == DBHelper.VALUE_TYPE_BINARY) {
1633        //blob
1634        writeBLOB(value,blobValue);
1635      }
1636      else if (valueType == DBHelper.VALUE_TYPE_STRING) {
1637        //clob
1638        String s = (String)value;
1639        writeCLOB(s,clobValue);
1640      }
1641      else {
1642        Assert.fail();
1643      }
1644    }
1645    catch(SQLException sqle) {
1646      throw new PersistenceException("can't create feature [step 2] in DB: ["+ sqle.getMessage()+"]");
1647    }
1648    catch(IOException ioe) {
1649      throw new PersistenceException("can't create feature [step 2] in DB: ["+ ioe.getMessage()+"]");
1650    }
1651    finally {
1652      DBHelper.cleanup(rsA);
1653      DBHelper.cleanup(stmtA);
1654    }
1655
1656  }
1657
1658
1659
1660  /**
1661   *  creates a feature with the specified type/key/value for the specified entity
1662   *  entitties are either LRs ot Annotations
1663   *  valid values are: boolean,
1664   *                    int,
1665   *                    long,
1666   *                    string,
1667   *                    float,
1668   *                    Object,
1669   *                    boolean List,
1670   *                    int List,
1671   *                    long List,
1672   *                    string List,
1673   *                    float List,
1674   *                    Object List
1675   *
1676   */
1677  private void createFeature(Long entityID, int entityType,String key, Object value)
1678    throws PersistenceException {
1679
1680    //1. what kind of feature value is this?
1681//System.out.println("key=["+key+"], val=["+value+"]");
1682    int valueType = findFeatureType(value);
1683
1684    //2. how many elements do we store?
1685    Vector elementsToStore = new Vector();
1686
1687    switch(valueType) {
1688      case DBHelper.VALUE_TYPE_NULL:
1689      case DBHelper.VALUE_TYPE_BINARY:
1690      case DBHelper.VALUE_TYPE_BOOLEAN:
1691      case DBHelper.VALUE_TYPE_FLOAT:
1692      case DBHelper.VALUE_TYPE_INTEGER:
1693      case DBHelper.VALUE_TYPE_LONG:
1694      case DBHelper.VALUE_TYPE_STRING:
1695        elementsToStore.add(value);
1696        break;
1697
1698      default:
1699        //arrays
1700        List arr = (List)value;
1701        Iterator itValues = arr.iterator();
1702
1703        while (itValues.hasNext()) {
1704          elementsToStore.add(itValues.next());
1705        }
1706
1707        //normalize , i.e. ignore arrays
1708        if (valueType == DBHelper.VALUE_TYPE_BINARY_ARR)
1709          valueType = DBHelper.VALUE_TYPE_BINARY;
1710        else if (valueType == DBHelper.VALUE_TYPE_BOOLEAN_ARR)
1711          valueType = DBHelper.VALUE_TYPE_BOOLEAN;
1712        else if (valueType == DBHelper.VALUE_TYPE_FLOAT_ARR)
1713          valueType = DBHelper.VALUE_TYPE_FLOAT;
1714        else if (valueType == DBHelper.VALUE_TYPE_INTEGER_ARR)
1715          valueType = DBHelper.VALUE_TYPE_INTEGER;
1716        else if (valueType == DBHelper.VALUE_TYPE_LONG_ARR)
1717          valueType = DBHelper.VALUE_TYPE_LONG;
1718        else if (valueType == DBHelper.VALUE_TYPE_STRING_ARR)
1719          valueType = DBHelper.VALUE_TYPE_STRING;
1720    }
1721
1722    //3. for all elements:
1723    for (int i=0; i< elementsToStore.size(); i++) {
1724
1725        Object currValue = elementsToStore.elementAt(i);
1726
1727        //3.1. create a dummy feature [LOB hack]
1728        Long featID = _createFeature(entityID,entityType,key,currValue,valueType);
1729
1730        //3.2. update CLOBs if needed
1731        if (valueType == DBHelper.VALUE_TYPE_STRING) {
1732          //does this string fit into a varchar2 or into clob?
1733          String s = (String)currValue;
1734          if (false == this.fitsInVarchar2(s)) {
1735            // Houston, we have a problem
1736            // put the string into a clob
1737            _updateFeatureLOB(featID,value,valueType);
1738          }
1739        }
1740        else if (valueType == DBHelper.VALUE_TYPE_BINARY) {
1741          //3.3. BLOBs
1742            _updateFeatureLOB(featID,value,valueType);
1743        }
1744    }
1745
1746
1747  }
1748
1749
1750
1751  /**
1752   *  checks if a String should be stores as VARCHAR2 or CLOB
1753   *  because the VARCHAR2 in Oracle is limited to 4000 <b>bytes</b>, not all
1754   *  the strings fit there. If a String is too long then it is store in the
1755   *  database as CLOB.
1756   *  Note that in the worst case 3 bytes are needed to represent a single character
1757   *  in a database with UTF8 encoding, which limits the string length to 4000/3
1758   *  (ORACLE_VARCHAR_LIMIT_BYTES)
1759   *  @see ORACLE_VARCHAR_LIMIT_BYTES
1760   */
1761  private boolean fitsInVarchar2(String s) {
1762
1763    return s.getBytes().length < this.ORACLE_VARCHAR_LIMIT_BYTES;
1764  }
1765
1766
1767
1768  /**
1769   *  helper metod
1770   *  iterates a FeatureMap and creates all its features in the database
1771   *   */
1772  private void createFeatures(Long entityID, int entityType, FeatureMap features)
1773    throws PersistenceException {
1774
1775      /* when some day Java has macros, this will be a macro */
1776      Set entries = features.entrySet();
1777      Iterator itFeatures = entries.iterator();
1778      while (itFeatures.hasNext()) {
1779        Map.Entry entry = (Map.Entry)itFeatures.next();
1780        String key = (String)entry.getKey();
1781        Object value = entry.getValue();
1782        createFeature(entityID,entityType,key,value);
1783      }
1784  }
1785
1786
1787
1788  /** get security information for LR . */
1789  public SecurityInfo getSecurityInfo(LanguageResource lr)
1790    throws PersistenceException {
1791
1792    //0. preconditions
1793    Assert.assertNotNull(lr);
1794    Assert.assertNotNull(lr.getLRPersistenceId());
1795    Assert.assertTrue(lr.getLRPersistenceId() instanceof Long);
1796    Assert.assertEquals(this,lr.getDataStore());
1797    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
1798                      lr instanceof DatabaseCorpusImpl);
1799
1800    PreparedStatement pstmt = null;
1801    ResultSet rs = null;
1802
1803    //1. read data
1804    Long userID = null;
1805    Long groupID = null;
1806    int  perm;
1807    try {
1808      String sql =  "   select lr_owner_user_id, "+
1809                    "          lr_owner_group_id, " +
1810                    "          lr_access_mode "+
1811                    "   from   "+gate.Gate.DB_OWNER+".t_lang_resource "+
1812                    "   where  lr_id = ?";
1813      pstmt = this.jdbcConn.prepareStatement(sql);
1814      pstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
1815      rs = pstmt.executeQuery();
1816
1817      if (false == rs.next()) {
1818        throw new PersistenceException("Invalid LR ID supplied - no data found");
1819      }
1820
1821      userID = new Long(rs.getLong("lr_owner_user_id"));
1822      groupID = new Long(rs.getLong("lr_owner_group_id"));
1823      perm = rs.getInt("lr_access_mode");
1824
1825      Assert.assertTrue(perm == SecurityInfo.ACCESS_GR_GW ||
1826                        perm == SecurityInfo.ACCESS_GR_OW ||
1827                        perm == SecurityInfo.ACCESS_OR_OW ||
1828                        perm == SecurityInfo.ACCESS_WR_GW);
1829    }
1830    catch(SQLException sqle) {
1831      throw new PersistenceException("Can't read document permissions from DB, error is [" +
1832                                      sqle.getMessage() +"]");
1833    }
1834    finally {
1835      DBHelper.cleanup(rs);
1836      DBHelper.cleanup(pstmt);
1837    }
1838
1839    //2. get data from AccessController
1840    User usr = null;
1841    Group grp = null;
1842    try {
1843      usr = this.ac.findUser(userID);
1844      grp = this.ac.findGroup(groupID);
1845    }
1846    catch (SecurityException se) {
1847      throw new PersistenceException("Invalid security settings found in DB [" +
1848                                      se.getMessage() +"]");
1849    }
1850
1851    //3. construct SecurityInfo
1852    SecurityInfo si = new SecurityInfo(perm,usr,grp);
1853
1854
1855    return si;
1856  }
1857
1858
1859
1860  /** set security information for LR . */
1861  public void setSecurityInfo(LanguageResource lr,SecurityInfo si)
1862    throws PersistenceException, SecurityException {
1863    throw new MethodNotImplementedException();
1864  }
1865
1866
1867
1868  /**
1869   *  helper method for getLR - reads LR of type Corpus
1870   */
1871  private DatabaseCorpusImpl readCorpus(Object lrPersistenceId)
1872    throws PersistenceException {
1873
1874    //0. preconditions
1875    Assert.assertNotNull(lrPersistenceId);
1876
1877    if (false == lrPersistenceId instanceof Long) {
1878      throw new IllegalArgumentException();
1879    }
1880
1881    //3. read from DB
1882    PreparedStatement pstmt = null;
1883    ResultSet rs = null;
1884    DatabaseCorpusImpl result = null;
1885
1886    try {
1887      String sql = " select lr_name " +
1888                   " from  "+Gate.DB_OWNER+".t_lang_resource " +
1889                   " where  lr_id = ? ";
1890      pstmt = this.jdbcConn.prepareStatement(sql);
1891      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
1892      pstmt.execute();
1893      rs = pstmt.getResultSet();
1894
1895      if (false == rs.next()) {
1896        //ooops mo data found
1897        throw new PersistenceException("Invalid LR ID supplied - no data found");
1898      }
1899
1900      //4. fill data
1901
1902      //4.1 name
1903      String lrName = rs.getString("lr_name");
1904      Assert.assertNotNull(lrName);
1905
1906      //4.8 features
1907      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_CORPUS);
1908
1909      DBHelper.cleanup(rs);
1910      DBHelper.cleanup(pstmt);
1911
1912      sql = " select doc_lr_id " +
1913            " from "+Gate.DB_OWNER+".t_document        doc, " +
1914            "      "+Gate.DB_OWNER+".t_corpus_document corpdoc, " +
1915            "      "+Gate.DB_OWNER+".t_corpus          corp " +
1916            " where doc.doc_id = corpdoc.cd_doc_id " +
1917            "       and corpdoc.cd_corp_id = corp.corp_id " +
1918            "       and corp_lr_id = ? ";
1919      pstmt = this.jdbcConn.prepareStatement(sql);
1920      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
1921      pstmt.execute();
1922      rs = pstmt.getResultSet();
1923
1924      Vector docLRIDs = new Vector();
1925      while (rs.next()) {
1926        Long docLRID = new Long(rs.getLong("doc_lr_id"));
1927        docLRIDs.add(docLRID);
1928      }
1929      DBHelper.cleanup(rs);
1930      DBHelper.cleanup(pstmt);
1931
1932
1933      Vector dbDocs = new Vector();
1934      for (int i=0; i< docLRIDs.size(); i++) {
1935        Long currLRID = (Long)docLRIDs.elementAt(i);
1936        //kalina: replaced by a Factory call, so the doc gets registered
1937        //properly in GATE. Otherwise strange behaviour results in the GUI
1938        //and no events come about it
1939//        Document dbDoc = (Document)getLr(DBHelper.DOCUMENT_CLASS,currLRID);
1940        FeatureMap params = Factory.newFeatureMap();
1941        params.put(DataStore.DATASTORE_FEATURE_NAME, this);
1942        params.put(DataStore.LR_ID_FEATURE_NAME, currLRID);
1943        Document dbDoc = (Document)
1944                    Factory.createResource(DBHelper.DOCUMENT_CLASS, params);
1945
1946
1947        dbDocs.add(dbDoc);
1948      }
1949
1950      result = new DatabaseCorpusImpl(lrName,
1951                                      this,
1952                                      (Long)lrPersistenceId,
1953                                      features,
1954                                      dbDocs);
1955    }
1956    catch(SQLException sqle) {
1957      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
1958    }
1959    catch(Exception e) {
1960      throw new PersistenceException(e);
1961    }
1962    finally {
1963      DBHelper.cleanup(rs);
1964      DBHelper.cleanup(pstmt);
1965    }
1966
1967    return result;
1968  }
1969
1970
1971  /** helper method for getLR - reads LR of type Document */
1972  private DatabaseDocumentImpl readDocument(Object lrPersistenceId)
1973    throws PersistenceException {
1974
1975    //0. preconditions
1976    Assert.assertNotNull(lrPersistenceId);
1977
1978    if (false == lrPersistenceId instanceof Long) {
1979      throw new IllegalArgumentException();
1980    }
1981
1982    // 1. dummy document to be initialized
1983    DatabaseDocumentImpl result = new DatabaseDocumentImpl(this.jdbcConn);
1984
1985    PreparedStatement pstmt = null;
1986    ResultSet rs = null;
1987
1988    //3. read from DB
1989    try {
1990      String sql = " select lr_name, " +
1991                   "        lrtp_type, " +
1992                   "        lr_id, " +
1993                   "        doc_id, " +
1994                   "        doc_url, " +
1995                   "        doc_start, " +
1996                   "        doc_end, " +
1997                   "        doc_is_markup_aware " +
1998                   " from  "+Gate.DB_OWNER+".v_document " +
1999                   " where  lr_id = ? ";
2000
2001      pstmt = this.jdbcConn.prepareStatement(sql);
2002      pstmt.setLong(1,((Long)lrPersistenceId).longValue());
2003      pstmt.execute();
2004      rs = pstmt.getResultSet();
2005
2006      if (false == rs.next()) {
2007        //ooops mo data found
2008        throw new PersistenceException("Invalid LR ID supplied - no data found");
2009      }
2010
2011      //4. fill data
2012
2013      //4.1 name
2014      String lrName = rs.getString("lr_name");
2015      Assert.assertNotNull(lrName);
2016      result.setName(lrName);
2017
2018      //4.2. markup aware
2019      long markup = rs.getLong("doc_is_markup_aware");
2020      Assert.assertTrue(markup == this.ORACLE_FALSE || markup == this.ORACLE_TRUE);
2021      if (markup == this.ORACLE_FALSE) {
2022        result.setMarkupAware(Boolean.FALSE);
2023      }
2024      else {
2025        result.setMarkupAware(Boolean.TRUE);
2026      }
2027
2028      //4.3 datastore
2029      result.setDataStore(this);
2030
2031      //4.4. persist ID
2032      Long persistID = new Long(rs.getLong("lr_id"));
2033      result.setLRPersistenceId(persistID);
2034
2035      //4.5  source url
2036      String url = rs.getString("doc_url");
2037      result.setSourceUrl(new URL(url));
2038
2039      //4.6. start offset
2040      Long start = null;
2041      long longVal = rs.getLong("doc_start");
2042      //null?
2043      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2044      if (false == rs.wasNull()) {
2045        start = new Long(longVal);
2046      }
2047      result.setSourceUrlStartOffset(start);
2048
2049      //4.7. end offset
2050      Long end = null;
2051      longVal = rs.getLong("doc_end");
2052      //null?
2053      //if NULL is stored in the DB, Oracle returns 0 which is not what we want
2054      if (false == rs.wasNull()) {
2055        end = new Long(longVal);
2056      }
2057      result.setSourceUrlEndOffset(end);
2058
2059      //4.8 features
2060      FeatureMap features = readFeatures((Long)lrPersistenceId,DBHelper.FEATURE_OWNER_DOCUMENT);
2061      result.setFeatures(features);
2062
2063      //4.9 set the nextAnnotationID correctly
2064      long doc_id = rs.getLong("doc_id");
2065
2066      DBHelper.cleanup(rs);
2067      DBHelper.cleanup(pstmt);
2068      sql = " select  max(ann_local_id) as max_ann_id" +
2069//            "         max(node_local_id) as max_node_id " +
2070            " from "+Gate.DB_OWNER+".t_annotation " +
2071            " where ann_doc_id = ?";
2072      pstmt = this.jdbcConn.prepareStatement(sql);
2073      pstmt.setLong(1,doc_id);
2074      pstmt.execute();
2075      rs = pstmt.getResultSet();
2076
2077      if (false == rs.next()) {
2078        //ooops mo data found
2079        throw new PersistenceException("Invalid LR ID supplied - no data found");
2080      }
2081
2082      int maxAnnID = rs.getInt("max_ann_id");
2083      result.setNextAnnotationId(maxAnnID+1);
2084      //node?
2085    }
2086    catch(SQLException sqle) {
2087      throw new PersistenceException("can't read LR from DB: ["+ sqle.getMessage()+"]");
2088    }
2089    catch(Exception e) {
2090      throw new PersistenceException(e);
2091    }
2092    finally {
2093      DBHelper.cleanup(rs);
2094      DBHelper.cleanup(pstmt);
2095    }
2096
2097    return result;
2098  }
2099
2100
2101
2102  /**
2103   *  reads the features of an entity
2104   *  entities are of type LR or Annotation
2105   */
2106  private FeatureMap readFeatures(Long entityID, int entityType)
2107    throws PersistenceException {
2108
2109    //0. preconditions
2110    Assert.assertNotNull(entityID);
2111    Assert.assertTrue(entityType == DBHelper.FEATURE_OWNER_ANNOTATION ||
2112                  entityType == DBHelper.FEATURE_OWNER_CORPUS ||
2113                  entityType == DBHelper.FEATURE_OWNER_DOCUMENT);
2114
2115
2116    PreparedStatement pstmt = null;
2117    ResultSet rs = null;
2118    FeatureMap fm = new SimpleFeatureMapImpl();
2119
2120    //1. read from DB
2121    try {
2122      String sql = " select v1.ft_key, " +
2123                   "        v1.ft_value_type, " +
2124                   "        v1.ft_number_value, " +
2125                   "        v1.ft_binary_value, " +
2126                   "        v1.ft_character_value, " +
2127                   "        v1.ft_long_character_value " +
2128                   " from  "+Gate.DB_OWNER+".t_feature v1 " +
2129                   " where  v1.ft_entity_id = ? " +
2130                   "        and v1.ft_entity_type = ? " +
2131                   " order by v1.ft_key,v1.ft_id";
2132
2133      pstmt = this.jdbcConn.prepareStatement(sql);
2134      pstmt.setLong(1,entityID.longValue());
2135      pstmt.setLong(2,entityType);
2136      pstmt.execute();
2137      rs = pstmt.getResultSet();
2138
2139      //3. fill feature map
2140      Vector arrFeatures = new Vector();
2141      String prevKey = null;
2142      String currKey = null;
2143      Object currFeature = null;
2144
2145
2146      while (rs.next()) {
2147        //NOTE: because there are LOBs in the resulset
2148        //the columns should be read in the order they appear
2149        //in the query
2150        currKey = rs.getString(1);
2151
2152        Long valueType = new Long(rs.getLong(2));
2153
2154        //we don't quite know what is the type of the NUMBER
2155        //stored in DB
2156        Object numberValue = null;
2157
2158        //for all numeric types + boolean -> read from DB as appropriate
2159        //Java object
2160        switch(valueType.intValue()) {
2161
2162          case DBHelper.VALUE_TYPE_BOOLEAN:
2163            numberValue = new Boolean(rs.getBoolean(3));
2164            break;
2165
2166          case DBHelper.VALUE_TYPE_FLOAT:
2167            numberValue = new Float(rs.getFloat(3));
2168            break;
2169
2170          case DBHelper.VALUE_TYPE_INTEGER:
2171            numberValue = new Integer(rs.getInt(3));
2172            break;
2173
2174          case DBHelper.VALUE_TYPE_LONG:
2175            numberValue = new Long(rs.getLong(3));
2176            break;
2177        }
2178
2179        //don't forget to read the rest of the current row
2180        Blob blobValue = rs.getBlob(4);
2181        String stringValue = rs.getString(5);
2182        Clob clobValue = rs.getClob(6);
2183
2184        switch(valueType.intValue()) {
2185
2186          case DBHelper.VALUE_TYPE_NULL:
2187            currFeature = null;
2188            break;
2189
2190          case DBHelper.VALUE_TYPE_BOOLEAN:
2191          case DBHelper.VALUE_TYPE_FLOAT:
2192          case DBHelper.VALUE_TYPE_INTEGER:
2193          case DBHelper.VALUE_TYPE_LONG:
2194            currFeature = numberValue;
2195            break;
2196
2197          case DBHelper.VALUE_TYPE_BINARY:
2198            currFeature = readBLOB(blobValue);
2199            break;
2200
2201          case DBHelper.VALUE_TYPE_STRING:
2202            //this one is tricky too
2203            //if the string is < 4000 bytes long then it's stored as varchar2
2204            //otherwise as CLOB
2205            if (null == stringValue) {
2206              //oops, we got CLOB
2207              StringBuffer temp = new StringBuffer();
2208              readCLOB(clobValue,temp);
2209              currFeature = temp.toString();
2210            }
2211            else {
2212              currFeature = stringValue;
2213            }
2214            break;
2215
2216          default:
2217            throw new PersistenceException("Invalid feature type found in DB");
2218        }//switch
2219
2220        //new feature or part of an array?
2221        if (currKey.equals(prevKey) && prevKey != null) {
2222          //part of array
2223          arrFeatures.add(currFeature);
2224        }
2225        else {
2226          //add prev feature to feature map
2227
2228          //is the prev feature an array or a single object?
2229          if (arrFeatures.size() > 1) {
2230            //put a clone, because this is a temp array that will
2231            //be cleared in few lines
2232            fm.put(prevKey, new Vector(arrFeatures));
2233          }
2234          else if (arrFeatures.size() == 1) {
2235            fm.put(prevKey,arrFeatures.elementAt(0));
2236          }
2237          else {
2238            //do nothing, this is the dummy feature
2239            ;
2240          }//if
2241
2242          //now clear the array from previous fesature(s) and put the new
2243          //one there
2244          arrFeatures.clear();
2245
2246          prevKey = currKey;
2247          arrFeatures.add(currFeature);
2248        }//if
2249      }//while
2250
2251      //add the last feature
2252      if (arrFeatures.size() > 1) {
2253        fm.put(currKey,arrFeatures);
2254      }
2255      else if (arrFeatures.size() == 1) {
2256        fm.put(currKey,arrFeatures.elementAt(0));
2257      }
2258    }//try
2259    catch(SQLException sqle) {
2260      throw new PersistenceException("can't read features from DB: ["+ sqle.getMessage()+"]");
2261    }
2262    catch(IOException ioe) {
2263      throw new PersistenceException("can't read features from DB: ["+ ioe.getMessage()+"]");
2264    }
2265    catch(ClassNotFoundException cnfe) {
2266      throw new PersistenceException("can't read features from DB: ["+ cnfe.getMessage()+"]");
2267    }
2268    finally {
2269      DBHelper.cleanup(rs);
2270      DBHelper.cleanup(pstmt);
2271    }
2272
2273    return fm;
2274  }
2275
2276
2277
2278  /**
2279   *  reads the ID of the database
2280   *  every database should have unique string ID
2281   */
2282  public String readDatabaseID() throws PersistenceException{
2283
2284    PreparedStatement pstmt = null;
2285    ResultSet rs = null;
2286    String  result = null;
2287
2288    //1. read from DB
2289    try {
2290      String sql = " select par_value_string " +
2291                   " from  "+Gate.DB_OWNER+".t_parameter " +
2292                   " where  par_key = ? ";
2293
2294      pstmt = this.jdbcConn.prepareStatement(sql);
2295      pstmt.setString(1,DBHelper.DB_PARAMETER_GUID);
2296      pstmt.execute();
2297      rs = pstmt.getResultSet();
2298
2299      if (false == rs.next()) {
2300        throw new PersistenceException("Can't read database parameter ["+
2301                                          DBHelper.DB_PARAMETER_GUID+"]");
2302      }
2303      result = rs.getString(1);
2304    }
2305    catch(SQLException sqle) {
2306        throw new PersistenceException("Can't read database parameter ["+
2307                                          sqle.getMessage()+"]");
2308    }
2309    finally {
2310      DBHelper.cleanup(rs);
2311      DBHelper.cleanup(pstmt);
2312    }
2313
2314    if (DEBUG) {
2315      Out.println("reult=["+result+"]");
2316    }
2317
2318    return result;
2319  }
2320
2321
2322
2323  /**
2324   *   checks if two databases are identical
2325   *   @see readDatabaseID()
2326   *   NOTE: the same database may be represented by different OracleDataStore instances
2327   *   but the IDs will be the same
2328   */
2329  public boolean equals(Object obj) {
2330
2331    if (false == obj instanceof OracleDataStore) {
2332      return false;
2333    }
2334
2335    OracleDataStore db2 = (OracleDataStore)obj;
2336
2337    if (false == this.getDatabaseID().equals(db2.getDatabaseID())) {
2338      return false;
2339    }
2340
2341    return true;
2342  }
2343
2344
2345
2346  /** helper for sync() - saves a Document in the database */
2347  private void syncDocument(Document doc) throws PersistenceException {
2348
2349    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2350    Assert.assertTrue(doc.getLRPersistenceId() instanceof Long);
2351
2352    Long lrID = (Long)doc.getLRPersistenceId();
2353    EventAwareLanguageResource dbDoc = (EventAwareLanguageResource)doc;
2354    //1. sync LR
2355    // only name can be changed here
2356    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
2357      _syncLR(lrID,doc.getName());
2358    }
2359
2360    //2. sync Document
2361    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_MAIN)) {
2362      _syncDocument(doc);
2363    }
2364
2365    //3. [optional] sync Content
2366    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.DOC_CONTENT)) {
2367      _syncDocumentContent(doc);
2368    }
2369
2370    //4. [optional] sync Features
2371    if (true == dbDoc.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
2372      _syncFeatures(doc);
2373    }
2374
2375    //5. [optional] delete from DB named sets that were removed from the document
2376    Collection removedSets = ((EventAwareDocument)dbDoc).getRemovedAnnotationSets();
2377    Collection addedSets = ((EventAwareDocument)dbDoc).getAddedAnnotationSets();
2378    if (false == removedSets.isEmpty() || false == addedSets.isEmpty()) {
2379      _syncAnnotationSets(doc,removedSets,addedSets);
2380    }
2381
2382    //6. [optional] sync Annotations
2383    _syncAnnotations(doc);
2384  }
2385
2386
2387
2388  /**
2389   *  helper for sync()
2390   *  NEVER call directly
2391   */
2392  private void _syncLR(Long lrID, String newName)
2393    throws PersistenceException {
2394
2395    CallableStatement stmt = null;
2396
2397    try {
2398      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.set_lr_name(?,?) }");
2399      stmt.setLong(1,lrID.longValue());
2400      stmt.setString(2,newName);
2401      stmt.execute();
2402    }
2403    catch(SQLException sqle) {
2404
2405      switch(sqle.getErrorCode()) {
2406        case DBHelper.X_ORACLE_INVALID_LR:
2407          throw new PersistenceException("can't set LR name in DB: [invalid LR ID]");
2408        default:
2409          throw new PersistenceException(
2410                "can't set LR name in DB: ["+ sqle.getMessage()+"]");
2411      }
2412
2413    }
2414    finally {
2415      DBHelper.cleanup(stmt);
2416    }
2417  }
2418
2419
2420
2421  /** helper for sync() - never call directly */
2422  private void _syncDocument(Document doc)
2423    throws PersistenceException {
2424
2425    Long lrID = (Long)doc.getLRPersistenceId();
2426
2427    CallableStatement stmt = null;
2428
2429    try {
2430      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
2431                                                    ".persist.update_document(?,?,?,?,?) }");
2432      stmt.setLong(1,lrID.longValue());
2433      stmt.setString(2,doc.getSourceUrl().toString());
2434      //do we have start offset?
2435      if (null==doc.getSourceUrlStartOffset()) {
2436        stmt.setNull(3,java.sql.Types.NUMERIC);
2437      }
2438      else {
2439        stmt.setLong(3,doc.getSourceUrlStartOffset().longValue());
2440      }
2441      //do we have end offset?
2442      if (null==doc.getSourceUrlEndOffset()) {
2443        stmt.setNull(4,java.sql.Types.NUMERIC);
2444      }
2445      else {
2446        stmt.setLong(4,doc.getSourceUrlEndOffset().longValue());
2447      }
2448
2449      stmt.setLong(5,true == doc.getMarkupAware().booleanValue() ? this.ORACLE_TRUE
2450                                                                  : this.ORACLE_FALSE);
2451
2452      stmt.execute();
2453    }
2454    catch(SQLException sqle) {
2455
2456      switch(sqle.getErrorCode()) {
2457        case DBHelper.X_ORACLE_INVALID_LR :
2458          throw new PersistenceException("invalid LR supplied: no such document: ["+
2459                                                            sqle.getMessage()+"]");
2460        default:
2461          throw new PersistenceException("can't change document data: ["+
2462                                                            sqle.getMessage()+"]");
2463      }
2464    }
2465    finally {
2466      DBHelper.cleanup(stmt);
2467    }
2468
2469  }
2470
2471
2472
2473  /** helper for sync() - never call directly */
2474  private void _syncDocumentContent(Document doc)
2475    throws PersistenceException {
2476
2477    PreparedStatement pstmt = null;
2478    ResultSet rs = null;
2479    Long docContID = null;
2480
2481    //1. read from DB
2482    try {
2483      String sql = " select dc_id " +
2484                   " from  "+Gate.DB_OWNER+".v_content " +
2485                   " where  lr_id = ? ";
2486
2487      pstmt = this.jdbcConn.prepareStatement(sql);
2488      pstmt.setLong(1,((Long)doc.getLRPersistenceId()).longValue());
2489      pstmt.execute();
2490      rs = pstmt.getResultSet();
2491
2492      if (false == rs.next()) {
2493        throw new PersistenceException("invalid LR ID supplied");
2494      }
2495
2496      //1, get DC_ID
2497      docContID = new Long(rs.getLong(1));
2498
2499      //2, update LOBs
2500      updateDocumentContent(docContID,doc.getContent());
2501
2502    }
2503    catch(SQLException sqle) {
2504      throw new PersistenceException("Cannot update document content ["+
2505                                      sqle.getMessage()+"]");
2506    }
2507    finally {
2508      DBHelper.cleanup(pstmt);
2509    }
2510  }
2511
2512
2513
2514  /** helper for sync() - never call directly */
2515  private void _syncAddedAnnotations(Document doc, AnnotationSet as, Collection changes)
2516    throws PersistenceException {
2517
2518    //0.preconditions
2519    Assert.assertNotNull(doc);
2520    Assert.assertNotNull(as);
2521    Assert.assertNotNull(changes);
2522    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2523    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
2524    Assert.assertTrue(changes.size() > 0);
2525
2526
2527    PreparedStatement pstmt = null;
2528    ResultSet rs = null;
2529    CallableStatement cstmt = null;
2530    Long lrID = (Long)doc.getLRPersistenceId();
2531    Long docID = null;
2532    Long asetID = null;
2533
2534    try {
2535      //1. get the a-set ID in the database
2536      String sql = " select as_id,  " +
2537                   "        as_doc_id " +
2538                   " from  "+Gate.DB_OWNER+".v_annotation_set " +
2539                   " where  lr_id = ? ";
2540      //do we have aset name?
2541      String clause = null;
2542      String name = as.getName();
2543      if (null != name) {
2544        clause =   "        and as_name = ? ";
2545      }
2546      else {
2547        clause =   "        and as_name is null ";
2548      }
2549      sql = sql + clause;
2550
2551      pstmt = this.jdbcConn.prepareStatement(sql);
2552      pstmt.setLong(1,lrID.longValue());
2553      if (null != name) {
2554        pstmt.setString(2,name);
2555      }
2556      pstmt.execute();
2557      rs = pstmt.getResultSet();
2558
2559      if (rs.next()) {
2560        asetID = new Long(rs.getLong("as_id"));
2561        docID = new Long(rs.getLong("as_doc_id"));
2562      }
2563      else {
2564        throw new PersistenceException("cannot find annotation set with" +
2565                                      " name=["+name+"] , LRID=["+lrID+"] in database");
2566      }
2567
2568      //3. insert the new annotations from this set
2569
2570      //3.1. prepare call
2571      cstmt = this.jdbcConn.prepareCall(
2572              "{ call "+Gate.DB_OWNER+".persist.create_annotation(?,?,?,?,?,?,?,?,?) }");
2573
2574      Long annGlobalID = null;
2575      Iterator it = changes.iterator();
2576
2577      while (it.hasNext()) {
2578
2579        //3.2. insert annotation
2580        Annotation ann = (Annotation)it.next();
2581
2582        Node start = (Node)ann.getStartNode();
2583        Node end = (Node)ann.getEndNode();
2584        String type = ann.getType();
2585
2586        cstmt.setLong(1,docID.longValue()); //annotations are linked with documents, not LRs!
2587        cstmt.setLong(2,ann.getId().longValue());
2588        cstmt.setLong(3,asetID.longValue());
2589        cstmt.setLong(4,start.getId().longValue());
2590        cstmt.setLong(5,start.getOffset().longValue());
2591        cstmt.setLong(6,end.getId().longValue());
2592        cstmt.setLong(7,end.getOffset().longValue());
2593        cstmt.setString(8,type);
2594        cstmt.registerOutParameter(9,java.sql.Types.BIGINT);
2595
2596        cstmt.execute();
2597        annGlobalID = new Long(cstmt.getLong(9));
2598
2599        //3.3. set annotation features
2600        FeatureMap features = ann.getFeatures();
2601        Assert.assertNotNull(features);
2602        createFeatures(annGlobalID,DBHelper.FEATURE_OWNER_ANNOTATION,features);
2603      }
2604    }
2605    catch(SQLException sqle) {
2606      throw new PersistenceException("can't add annotations in DB : ["+
2607                                      sqle.getMessage()+"]");
2608    }
2609    finally {
2610      DBHelper.cleanup(rs);
2611      DBHelper.cleanup(pstmt);
2612      DBHelper.cleanup(cstmt);
2613    }
2614  }
2615
2616
2617
2618  /** helper for sync() - never call directly */
2619  private void _syncChangedAnnotations(Document doc,AnnotationSet as, Collection changes)
2620    throws PersistenceException {
2621
2622    //technically this approach sux
2623    //at least it works
2624
2625    //1. delete
2626    _syncRemovedAnnotations(doc,as,changes);
2627    //2. recreate
2628    _syncAddedAnnotations(doc,as,changes);
2629  }
2630
2631
2632
2633  /** helper for sync() - never call directly */
2634  private void _syncRemovedAnnotations(Document doc,AnnotationSet as, Collection changes)
2635    throws PersistenceException {
2636    //0.preconditions
2637    Assert.assertNotNull(doc);
2638    Assert.assertNotNull(as);
2639    Assert.assertNotNull(changes);
2640    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2641    Assert.assertTrue(as instanceof DatabaseAnnotationSetImpl);
2642    Assert.assertTrue(changes.size() > 0);
2643
2644
2645    PreparedStatement pstmt = null;
2646    ResultSet rs = null;
2647    CallableStatement cstmt = null;
2648    Long lrID = (Long)doc.getLRPersistenceId();
2649    Long docID = null;
2650    Long asetID = null;
2651
2652    try {
2653      //1. get the a-set ID in the database
2654      String sql = " select as_id,  " +
2655                   "        as_doc_id " +
2656                   " from  "+Gate.DB_OWNER+".v_annotation_set " +
2657                   " where  lr_id = ? ";
2658      //do we have aset name?
2659      String clause = null;
2660      String name = as.getName();
2661      if (null != name) {
2662        clause =   "        and as_name = ? ";
2663      }
2664      else {
2665        clause =   "        and as_name is null ";
2666      }
2667      sql = sql + clause;
2668
2669      pstmt = this.jdbcConn.prepareStatement(sql);
2670      pstmt.setLong(1,lrID.longValue());
2671      if (null != name) {
2672        pstmt.setString(2,name);
2673      }
2674      pstmt.execute();
2675      rs = pstmt.getResultSet();
2676
2677      if (rs.next()) {
2678        asetID = new Long(rs.getLong("as_id"));
2679        docID = new Long(rs.getLong("as_doc_id"));
2680      }
2681      else {
2682        throw new PersistenceException("cannot find annotation set with" +
2683                                      " name=["+name+"] , LRID=["+lrID+"] in database");
2684      }
2685
2686      //3. delete the removed annotations from this set
2687
2688      //3.1. prepare call
2689      cstmt = this.jdbcConn.prepareCall(
2690              "{ call "+Gate.DB_OWNER+".persist.delete_annotation(?,?) }");
2691
2692
2693      Iterator it = changes.iterator();
2694
2695      while (it.hasNext()) {
2696
2697        //3.2. insert annotation
2698        Annotation ann = (Annotation)it.next();
2699
2700        cstmt.setLong(1,docID.longValue()); //annotations are linked with documents, not LRs!
2701        cstmt.setLong(2,ann.getId().longValue());
2702        cstmt.execute();
2703      }
2704    }
2705    catch(SQLException sqle) {
2706      throw new PersistenceException("can't delete annotations in DB : ["+
2707                                      sqle.getMessage()+"]");
2708    }
2709    finally {
2710      DBHelper.cleanup(rs);
2711      DBHelper.cleanup(pstmt);
2712      DBHelper.cleanup(cstmt);
2713    }
2714  }
2715
2716
2717
2718  /** helper for sync() - never call directly */
2719  private void _syncAnnotationSets(Document doc,Collection removedSets,Collection addedSets)
2720    throws PersistenceException {
2721
2722    //0. preconditions
2723    Assert.assertNotNull(doc);
2724    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2725    Assert.assertNotNull(doc.getLRPersistenceId());
2726    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
2727                      this.getDatabaseID());
2728    Assert.assertNotNull(removedSets);
2729    Assert.assertNotNull(addedSets);
2730
2731    Long lrID = (Long)doc.getLRPersistenceId();
2732
2733    //1. delete from DB removed a-sets
2734    CallableStatement cstmt = null;
2735
2736    try {
2737      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
2738                                                ".persist.delete_annotation_set(?,?) }");
2739
2740      Iterator it = removedSets.iterator();
2741      while (it.hasNext()) {
2742        String setName = (String)it.next();
2743
2744        cstmt.setLong(1,lrID.longValue());
2745        cstmt.setString(2,setName);
2746        cstmt.execute();
2747      }
2748    }
2749    catch(SQLException sqle) {
2750      throw new PersistenceException("can't remove annotation set from DB: ["+ sqle.getMessage()+"]");
2751    }
2752    finally {
2753      DBHelper.cleanup(cstmt);
2754    }
2755
2756    //2. create in DB new a-sets
2757    Iterator it = addedSets.iterator();
2758    while (it.hasNext()) {
2759      String setName = (String)it.next();
2760      AnnotationSet aset = doc.getAnnotations(setName);
2761
2762      Assert.assertNotNull(aset);
2763      Assert.assertTrue(aset instanceof DatabaseAnnotationSetImpl);
2764
2765      createAnnotationSet(lrID,aset);
2766    }
2767  }
2768
2769
2770
2771  /** helper for sync() - never call directly */
2772  private void _syncAnnotations(Document doc)
2773    throws PersistenceException {
2774
2775    //0. preconditions
2776    Assert.assertNotNull(doc);
2777    Assert.assertTrue(doc instanceof DatabaseDocumentImpl);
2778    Assert.assertNotNull(doc.getLRPersistenceId());
2779    Assert.assertEquals(((DatabaseDataStore)doc.getDataStore()).getDatabaseID(),
2780                      this.getDatabaseID());
2781
2782
2783    //1. get the sets read from the DB for this document
2784    //chnaged annotations can occur only in such sets
2785    Collection loadedSets = ((EventAwareDocument)doc).getLoadedAnnotationSets();
2786
2787    Iterator it = loadedSets.iterator();
2788    while (it.hasNext()) {
2789
2790      AnnotationSet as = (AnnotationSet)it.next();
2791      EventAwareAnnotationSet eas = (EventAwareAnnotationSet)as;
2792      Assert.assertNotNull(as);
2793
2794      Collection anns = null;
2795      anns = eas.getAddedAnnotations();
2796      Assert.assertNotNull(anns);
2797      if (anns.size()>0) {
2798        _syncAddedAnnotations(doc,as,anns);
2799      }
2800
2801      anns = eas.getRemovedAnnotations();
2802      Assert.assertNotNull(anns);
2803      if (anns.size()>0) {
2804        _syncRemovedAnnotations(doc,as,anns);
2805      }
2806
2807      anns = eas.getChangedAnnotations();
2808      Assert.assertNotNull(anns);
2809      if (anns.size()>0) {
2810        _syncChangedAnnotations(doc,as,anns);
2811      }
2812    }
2813  }
2814
2815
2816
2817  /** helper for sync() - never call directly */
2818  private void _syncFeatures(LanguageResource lr)
2819    throws PersistenceException {
2820
2821    //0. preconditions
2822    Assert.assertNotNull(lr);
2823    Assert.assertNotNull(lr.getLRPersistenceId());
2824    Assert.assertEquals(((DatabaseDataStore)lr.getDataStore()).getDatabaseID(),
2825                      this.getDatabaseID());
2826    Assert.assertTrue(lr instanceof Document || lr instanceof Corpus);
2827    //we have to be in the context of transaction
2828
2829    //1, get ID  in the DB
2830    Long lrID = (Long)lr.getLRPersistenceId();
2831    int  entityType;
2832
2833    //2. delete features
2834    CallableStatement stmt = null;
2835    try {
2836      Assert.assertTrue(false == this.jdbcConn.getAutoCommit());
2837      stmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+
2838                                                    ".persist.delete_features(?,?) }");
2839      stmt.setLong(1,lrID.longValue());
2840
2841      if (lr instanceof Document) {
2842        entityType = DBHelper.FEATURE_OWNER_DOCUMENT;
2843      }
2844      else if (lr instanceof Corpus) {
2845        entityType = DBHelper.FEATURE_OWNER_CORPUS;
2846      }
2847      else {
2848        throw new IllegalArgumentException();
2849      }
2850
2851      stmt.setInt(2,entityType);
2852      stmt.execute();
2853    }
2854    catch(SQLException sqle) {
2855      throw new PersistenceException("can't delete features in DB: ["+ sqle.getMessage()+"]");
2856    }
2857    finally {
2858      DBHelper.cleanup(stmt);
2859    }
2860
2861    //3. recreate them
2862    createFeatures(lrID,entityType, lr.getFeatures());
2863
2864  }
2865
2866
2867
2868  /** helper for sync() - saves a Corpus in the database */
2869  private void syncCorpus(Corpus corp) throws PersistenceException {
2870
2871    //0. preconditions
2872    Assert.assertNotNull(corp);
2873    Assert.assertTrue(corp instanceof DatabaseCorpusImpl);
2874    Assert.assertEquals(this,corp.getDataStore());
2875    Assert.assertNotNull(corp.getLRPersistenceId());
2876
2877    EventAwareLanguageResource dbCorpus = (EventAwareLanguageResource)corp;
2878
2879    //1. sync the corpus name?
2880    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_NAME)) {
2881      _syncLR((Long)corp.getLRPersistenceId(),corp.getName());
2882    }
2883
2884    //2. sync the corpus features?
2885    if (dbCorpus.isResourceChanged(EventAwareLanguageResource.RES_FEATURES)) {
2886      _syncFeatures(corp);
2887    }
2888
2889    //3. get all documents
2890    Iterator it = corp.iterator();
2891    while (it.hasNext()) {
2892      Document dbDoc = (Document)it.next();
2893//System.out.println("found doc ["+dbDoc.getName()+"]");
2894      //adopt/sync?
2895      if (null == dbDoc.getLRPersistenceId()) {
2896        //doc was never adopted, adopt it
2897
2898        //3.1 remove the transient doc from the corpus
2899        it.remove();
2900
2901        //3.2 get the security info for the corpus
2902        SecurityInfo si = getSecurityInfo(corp);
2903
2904
2905        Document adoptedDoc = null;
2906        try {
2907          //3.3. adopt the doc with the sec info
2908//System.out.println("adopting ["+dbDoc.getName()+"] ...");
2909          //don't open a new transaction, since sync() already has opended one
2910          adoptedDoc = (Document)_adopt(dbDoc,si,false);
2911
2912          //3.4. add doc to corpus in DB
2913          addDocumentToCorpus((Long)adoptedDoc.getLRPersistenceId(),
2914                              (Long)corp.getLRPersistenceId());
2915        }
2916        catch(SecurityException se) {
2917          throw new PersistenceException(se);
2918        }
2919
2920        //3.5 add back to corpus the new DatabaseDocument
2921        corp.add(adoptedDoc);
2922      }
2923      else {
2924        //don't open a new transaction, the sync() called for corpus has already
2925        //opened one
2926        try {
2927          _sync(dbDoc,false);
2928
2929          //if the document is form the same DS but did not belong to the corpus add it now
2930          //NOTE: if the document already belongs to the corpus then nothing will be changed
2931          //in the DB
2932          addDocumentToCorpus((Long)dbDoc.getLRPersistenceId(),
2933                              (Long)corp.getLRPersistenceId());
2934        }
2935        catch(SecurityException se) {
2936          gate.util.Err.prln("document cannot be synced: ["+se.getMessage()+"]");
2937        }
2938      }
2939    }
2940  }
2941
2942
2943
2944  /**
2945   * Try to acquire exlusive lock on a resource from the persistent store.
2946   * Always call unlockLR() when the lock is no longer needed
2947   */
2948  public boolean lockLr(LanguageResource lr)
2949  throws PersistenceException,SecurityException {
2950
2951    //0. preconditions
2952    Assert.assertNotNull(lr);
2953    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
2954                      lr instanceof DatabaseCorpusImpl);
2955    Assert.assertNotNull(lr.getLRPersistenceId());
2956    Assert.assertEquals(lr.getDataStore(),this);
2957
2958    //1. delegate
2959    return _lockLr((Long)lr.getLRPersistenceId());
2960  }
2961
2962
2963
2964  /**
2965   *  helper for lockLR()
2966   *  never call directly
2967   */
2968  private boolean _lockLr(Long lrID)
2969  throws PersistenceException,SecurityException {
2970
2971    //0. preconditions
2972    Assert.assertNotNull(lrID);
2973
2974    //1. check session
2975    if (null == this.session) {
2976      throw new SecurityException("session not set");
2977    }
2978
2979    if (false == this.ac.isValidSession(this.session)) {
2980      throw new SecurityException("invalid session supplied");
2981    }
2982
2983    //2. check permissions
2984    if (false == canWriteLR(lrID)) {
2985      throw new SecurityException("no write access granted to the user");
2986    }
2987
2988    //3. try to lock
2989    CallableStatement cstmt = null;
2990    boolean lockSucceeded = false;
2991
2992    try {
2993      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.lock_lr(?,?,?,?) }");
2994      cstmt.setLong(1,lrID.longValue());
2995      cstmt.setLong(2,this.session.getUser().getID().longValue());
2996      cstmt.setLong(3,this.session.getGroup().getID().longValue());
2997      cstmt.registerOutParameter(4,java.sql.Types.NUMERIC);
2998      cstmt.execute();
2999
3000      lockSucceeded = cstmt.getLong(4) == this.ORACLE_TRUE
3001                                          ? true
3002                                          : false;
3003    }
3004    catch(SQLException sqle) {
3005
3006      switch(sqle.getErrorCode()) {
3007        case DBHelper.X_ORACLE_INVALID_LR:
3008          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
3009        default:
3010          throw new PersistenceException(
3011                "can't lock LR in DB : ["+ sqle.getMessage()+"]");
3012      }
3013    }
3014    finally {
3015      DBHelper.cleanup(cstmt);
3016    }
3017
3018    return lockSucceeded;
3019  }
3020
3021
3022
3023  /**
3024   * Releases the exlusive lock on a resource from the persistent store.
3025   */
3026  public void unlockLr(LanguageResource lr)
3027  throws PersistenceException,SecurityException {
3028
3029    //0. preconditions
3030    Assert.assertNotNull(lr);
3031    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
3032                      lr instanceof DatabaseCorpusImpl);
3033    Assert.assertNotNull(lr.getLRPersistenceId());
3034    Assert.assertEquals(lr.getDataStore(),this);
3035
3036    //1. check session
3037    if (null == this.session) {
3038      throw new SecurityException("session not set");
3039    }
3040
3041    if (false == this.ac.isValidSession(this.session)) {
3042      throw new SecurityException("invalid session supplied");
3043    }
3044
3045    //2. check permissions
3046    if (false == canWriteLR(lr.getLRPersistenceId())) {
3047      throw new SecurityException("no write access granted to the user");
3048    }
3049
3050    //3. try to unlock
3051    CallableStatement cstmt = null;
3052    boolean lockSucceeded = false;
3053
3054    try {
3055      cstmt = this.jdbcConn.prepareCall("{ call "+Gate.DB_OWNER+".persist.unlock_lr(?,?) }");
3056      cstmt.setLong(1,((Long)lr.getLRPersistenceId()).longValue());
3057      cstmt.setLong(2,this.session.getUser().getID().longValue());
3058      cstmt.execute();
3059    }
3060    catch(SQLException sqle) {
3061
3062      switch(sqle.getErrorCode()) {
3063        case DBHelper.X_ORACLE_INVALID_LR:
3064          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
3065        default:
3066          throw new PersistenceException(
3067                "can't unlock LR in DB : ["+ sqle.getMessage()+"]");
3068      }
3069    }
3070    finally {
3071      DBHelper.cleanup(cstmt);
3072    }
3073  }
3074
3075
3076
3077  /**
3078   * Releases the exlusive lock on a resource from the persistent store.
3079   */
3080  private User getLockingUser(LanguageResource lr)
3081    throws PersistenceException,SecurityException {
3082
3083    //0. preconditions
3084    Assert.assertNotNull(lr);
3085    Assert.assertTrue(lr instanceof DatabaseDocumentImpl ||
3086                      lr instanceof DatabaseCorpusImpl);
3087    Assert.assertNotNull(lr.getLRPersistenceId());
3088    Assert.assertEquals(lr.getDataStore(),this);
3089
3090    //delegate
3091    return getLockingUser((Long)lr.getLRPersistenceId());
3092  }
3093
3094
3095
3096  /**
3097   * Releases the exlusive lock on a resource from the persistent store.
3098   */
3099  private User getLockingUser(Long lrID)
3100  throws PersistenceException,SecurityException {
3101
3102    //1. check session
3103    if (null == this.session) {
3104      throw new SecurityException("session not set");
3105    }
3106
3107    if (false == this.ac.isValidSession(this.session)) {
3108      throw new SecurityException("invalid session supplied");
3109    }
3110
3111    //3. read from DB
3112    PreparedStatement pstmt = null;
3113    Long userID = null;
3114    ResultSet rs = null;
3115
3116    try {
3117      String sql = "  select  nvl(lr_locking_user_id,0) as user_id" +
3118                    " from "+Gate.DB_OWNER+".t_lang_resource " +
3119                    " where   lr_id = ?";
3120
3121      pstmt = this.jdbcConn.prepareStatement(sql);
3122      pstmt.setLong(1,lrID.longValue());
3123      pstmt.execute();
3124      rs = pstmt.getResultSet();
3125
3126      if (false == rs.next()) {
3127        throw new PersistenceException("LR not found in DB");
3128      }
3129
3130      long result = rs.getLong("user_id");
3131
3132      return result == 0  ? null
3133                          : this.ac.findUser(new Long(result));
3134    }
3135    catch(SQLException sqle) {
3136      throw new PersistenceException("can't get locking user from DB : ["+ sqle.getMessage()+"]");
3137    }
3138    finally {
3139      DBHelper.cleanup(rs);
3140      DBHelper.cleanup(pstmt);
3141    }
3142  }
3143
3144
3145
3146  /**
3147   *   adds document to corpus in the database
3148   *   if the document is already part of the corpus nothing
3149   *   changes
3150   */
3151  private void addDocumentToCorpus(Long docID,Long corpID)
3152  throws PersistenceException,SecurityException {
3153
3154    //0. preconditions
3155    Assert.assertNotNull(docID);
3156    Assert.assertNotNull(corpID);
3157
3158    //1. check session
3159    if (null == this.session) {
3160      throw new SecurityException("session not set");
3161    }
3162
3163    if (false == this.ac.isValidSession(this.session)) {
3164      throw new SecurityException("invalid session supplied");
3165    }
3166
3167    //2. check permissions
3168    if (false == canWriteLR(corpID)) {
3169      throw new SecurityException("no write access granted to the user");
3170    }
3171
3172    if (false == canWriteLR(docID)) {
3173      throw new SecurityException("no write access granted to the user");
3174    }
3175
3176    //3. database
3177    CallableStatement cstmt = null;
3178
3179    try {
3180      cstmt = this.jdbcConn.prepareCall("{ call "+
3181                                  Gate.DB_OWNER+".persist.add_document_to_corpus(?,?) }");
3182      cstmt.setLong(1,docID.longValue());
3183      cstmt.setLong(2,corpID.longValue());
3184      cstmt.execute();
3185    }
3186    catch(SQLException sqle) {
3187
3188      switch(sqle.getErrorCode()) {
3189        case DBHelper.X_ORACLE_INVALID_LR:
3190          throw new PersistenceException("invalid LR ID supplied ["+sqle.getMessage()+"]");
3191        default:
3192          throw new PersistenceException(
3193                "can't add document to corpus : ["+ sqle.getMessage()+"]");
3194      }
3195    }
3196    finally {
3197      DBHelper.cleanup(cstmt);
3198    }
3199  }
3200
3201
3202
3203  /**
3204   *   unloads a LR from the GUI
3205   */
3206  private void unloadLR(Long lrID)
3207  throws GateException{
3208
3209    //0. preconfitions
3210    Assert.assertNotNull(lrID);
3211
3212    //1. get all LRs in the system
3213    List resources = Gate.getCreoleRegister().getAllInstances("gate.LanguageResource");
3214
3215    Iterator it = resources.iterator();
3216    while (it.hasNext()) {
3217      LanguageResource lr = (LanguageResource)it.next();
3218      if (lrID.equals(lr.getLRPersistenceId()) &&
3219          this.equals(lr.getDataStore())) {
3220        //found it - unload it
3221        Factory.deleteResource(lr);
3222        break;
3223      }
3224    }
3225  }
3226
3227}
3228