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