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