-
-
Notifications
You must be signed in to change notification settings - Fork 179
/
Collection.java
2259 lines (1977 loc) · 88.2 KB
/
Collection.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2013 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.collections;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.TreeMap;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.log4j.Logger;
import org.exist.Database;
import org.exist.EXistException;
import org.exist.Indexer;
import org.exist.collections.triggers.*;
import org.exist.dom.*;
import org.exist.security.Account;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.PermissionFactory;
import org.exist.security.Subject;
import org.exist.storage.*;
import org.exist.storage.cache.Cacheable;
import org.exist.storage.index.BFile;
import org.exist.storage.io.VariableByteInput;
import org.exist.storage.io.VariableByteOutputStream;
import org.exist.storage.lock.*;
import org.exist.storage.sync.Sync;
import org.exist.storage.txn.Txn;
import org.exist.util.Configuration;
import org.exist.util.LockException;
import org.exist.util.MimeType;
import org.exist.util.SyntaxException;
import org.exist.util.XMLReaderObjectFactory;
import org.exist.util.XMLReaderObjectFactory.VALIDATION_SETTING;
import org.exist.util.hashtable.ObjectHashSet;
import org.exist.util.serializer.DOMStreamer;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* This class represents a collection in the database. A collection maintains a list of
* sub-collections and documents, and provides the methods to store/remove resources.
*
* Collections are shared between {@link org.exist.storage.DBBroker} instances. The caller
* is responsible to lock/unlock the collection. Call {@link DBBroker#openCollection(XmldbURI, int)}
* to get a collection with a read or write lock and {@link #release(int)} to release the lock.
*
* @author wolf
*/
public class Collection extends Observable implements Comparable<Collection>, Cacheable {
public static int LENGTH_COLLECTION_ID = 4; //sizeof int
public static final int POOL_PARSER_THRESHOLD = 500;
private final static int SHALLOW_SIZE = 550;
private final static int DOCUMENT_SIZE = 450;
private final static Logger LOG = Logger.getLogger(Collection.class);
public final static int UNKNOWN_COLLECTION_ID = -1;
// Internal id
private int collectionId = UNKNOWN_COLLECTION_ID;
// the documents contained in this collection
private Map<String, DocumentImpl> documents = new TreeMap<String, DocumentImpl>();
// the path of this collection
private XmldbURI path;
// stores child-collections with their storage address
private ObjectHashSet<XmldbURI> subCollections = new ObjectHashSet<XmldbURI>(19);
// Storage address of the collection in the BFile
private long address = BFile.UNKNOWN_ADDRESS;
// creation time
private long created = 0;
private Observer[] observers = null;
private volatile boolean collectionConfigEnabled = true;
private boolean triggersEnabled = true;
// fields required by the collections cache
private int refCount;
private int timestamp;
private final Lock lock;
/** user-defined Reader */
private XMLReader userReader;
/** is this a temporary collection? */
private boolean isTempCollection;
private Permission permissions;
public Collection(final DBBroker broker, final XmldbURI path) {
//The permissions assigned to this collection
permissions = PermissionFactory.getDefaultCollectionPermission();
setPath(path);
lock = new ReentrantReadWriteLock(path);
}
public boolean isTriggersEnabled() {
return triggersEnabled;
}
public final void setPath(XmldbURI path) {
path = path.toCollectionPathURI();
//TODO : see if the URI resolves against DBBroker.TEMP_COLLECTION
isTempCollection = path.getRawCollectionPath().equals(XmldbURI.TEMP_COLLECTION);
this.path=path;
}
public Lock getLock() {
return lock;
}
/**
* Add a new sub-collection to the collection.
*
*/
public void addCollection(final DBBroker broker, final Collection child, final boolean isNew) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.WRITE)) {
throw new PermissionDeniedException("Permission to write to Collection denied for " + this.getURI());
}
final XmldbURI childName = child.getURI().lastSegment();
if(!subCollections.contains(childName)) {
subCollections.add(childName);
}
if(isNew) {
child.setCreationTime(System.currentTimeMillis());
}
}
public boolean hasChildCollection(final DBBroker broker, final XmldbURI path) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
return subCollections.contains(path);
}
public abstract class CollectionEntry {
private final XmldbURI uri;
private Permission permissions;
private long created = -1;
protected CollectionEntry(final XmldbURI uri, final Permission permissions) {
this.uri = uri;
this.permissions = permissions;
}
public abstract void readMetadata(DBBroker broker);
public abstract void read(VariableByteInput is) throws IOException;
public XmldbURI getUri() {
return uri;
}
public long getCreated() {
return created;
}
protected void setCreated(final long created) {
this.created = created;
}
public Permission getPermissions() {
return permissions;
}
protected void setPermissions(final Permission permissions) {
this.permissions = permissions;
}
}
public class SubCollectionEntry extends CollectionEntry {
public SubCollectionEntry(final XmldbURI uri) {
super(uri, PermissionFactory.getDefaultCollectionPermission());
}
@Override
public void readMetadata(final DBBroker broker) {
broker.readCollectionEntry(this);
}
@Override
public void read(final VariableByteInput is) throws IOException {
is.skip(1);
final int collLen = is.readInt();
for(int i = 0; i < collLen; i++) {
is.readUTF();
}
getPermissions().read(is);
setCreated(is.readLong());
}
public void read(final Collection collection) {
setPermissions(collection.getPermissionsNoLock());
setCreated(collection.getCreationTime());
}
}
public class DocumentEntry extends CollectionEntry {
public DocumentEntry(final DocumentImpl document) {
super(document.getURI(), document.getPermissions());
setCreated(document.getMetadata().getCreated());
}
@Override
public void readMetadata(final DBBroker broker) {
}
@Override
public void read(final VariableByteInput is) throws IOException {
}
}
public List<CollectionEntry> getEntries(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
final List<CollectionEntry> list = new ArrayList<CollectionEntry>();
final Iterator<XmldbURI> subCollectionIterator = subCollections.iterator();
while(subCollectionIterator.hasNext()) {
final XmldbURI subCollectionURI = subCollectionIterator.next();
final CollectionEntry entry = new SubCollectionEntry(subCollectionURI);
entry.readMetadata(broker);
list.add(entry);
}
for(final DocumentImpl document : documents.values()) {
final CollectionEntry entry = new DocumentEntry(document);
entry.readMetadata(broker);
list.add(entry);
}
return list;
}
public CollectionEntry getSubCollectionEntry(final DBBroker broker, final String name) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
final XmldbURI subCollectionURI = getURI().append(name);
final CollectionEntry entry = new SubCollectionEntry(subCollectionURI);
entry.readMetadata(broker);
return entry;
}
public CollectionEntry getResourceEntry(final DBBroker broker, final String name) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
final CollectionEntry entry = new DocumentEntry(documents.get(name));
entry.readMetadata(broker);
return entry;
}
/**
* Returns true if this is a temporary collection. By default,
* the temporary collection is in /db/system/temp.
*
* @return A boolean where true means the collection is temporary.
*/
public boolean isTempCollection() {
return isTempCollection;
}
/**
* Closes the collection, i.e. releases the lock held by
* the current thread. This is a shortcut for getLock().release().
*/
public void release(final int mode) {
getLock().release(mode);
}
/**
* Update the specified child-collection.
*
* @param child
*/
public void update(final DBBroker broker, final Collection child) throws PermissionDeniedException {
final XmldbURI childName = child.getURI().lastSegment();
subCollections.remove(childName);
subCollections.add(childName);
}
/**
* Add a document to the collection.
*
* @param doc
*/
public void addDocument(final Txn transaction, final DBBroker broker, final DocumentImpl doc) throws PermissionDeniedException {
addDocument(transaction, broker, doc, null);
}
/**
* @param oldDoc if not null, then this document is replacing another and so WRITE access on the collection is not required,
* just WRITE access on the old document
*/
private void addDocument(final Txn transaction, final DBBroker broker, final DocumentImpl doc, final DocumentImpl oldDoc) throws PermissionDeniedException {
if(oldDoc == null) {
/* create */
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.WRITE)) {
throw new PermissionDeniedException("Permission to write to Collection denied for " + this.getURI());
}
} else {
/* update-replace */
if(!oldDoc.getPermissions().validate(broker.getSubject(), Permission.WRITE)) {
throw new PermissionDeniedException("Permission to write to overwrite document: " + oldDoc.getURI());
}
}
if (doc.getDocId() == DocumentImpl.UNKNOWN_DOCUMENT_ID) {
try {
doc.setDocId(broker.getNextResourceId(transaction, this));
} catch(final EXistException e) {
LOG.error("Collection error " + e.getMessage(), e);
// TODO : re-raise the exception ? -pb
return;
}
}
documents.put(doc.getFileURI().getRawCollectionPath(), doc);
}
/**
* Removes the document from the internal list of resources, but
* doesn't delete the document object itself.
*
* @param doc
*/
public void unlinkDocument(final DBBroker broker, final DocumentImpl doc) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.WRITE)) {
throw new PermissionDeniedException("Permission denied to remove document from collection: " + path);
}
documents.remove(doc.getFileURI().getRawCollectionPath());
}
/**
* Return an iterator over all sub-collections.
*
* The list of sub-collections is copied first, so modifications
* via the iterator have no effect.
*
* @return An iterator over the collections
*/
public Iterator<XmldbURI> collectionIterator(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission to list sub-collections denied on " + this.getURI());
}
try {
getLock().acquire(Lock.READ_LOCK);
return subCollections.stableIterator();
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
return null;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
/**
* Return an iterator over all sub-collections.
*
* The list of sub-collections is copied first, so modifications
* via the iterator have no effect.
*
* @return An iterator over the collections
*/
public Iterator<XmldbURI> collectionIteratorNoLock(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission to list sub-collections denied on " + this.getURI());
}
return subCollections.stableIterator();
}
/**
* Return the collections below this collection
*
* @return List
*/
public List<Collection> getDescendants(final DBBroker broker, final Subject user) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission to list sub-collections denied on " + this.getURI());
}
final ArrayList<Collection> collectionList = new ArrayList<Collection>(subCollections.size());
try {
getLock().acquire(Lock.READ_LOCK);
for(final Iterator<XmldbURI> i = subCollections.iterator(); i.hasNext(); ) {
final XmldbURI childName = i.next();
//TODO : resolve URI !
final Collection child = broker.getCollection(path.append(childName));
if(getPermissionsNoLock().validate(user, Permission.READ)) {
collectionList.add(child);
if(child.getChildCollectionCount(broker) > 0) {
//Recursive call
collectionList.addAll(child.getDescendants(broker, user));
}
}
}
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
} finally {
getLock().release(Lock.READ_LOCK);
}
return collectionList;
}
public MutableDocumentSet allDocs(final DBBroker broker, final MutableDocumentSet docs, final boolean recursive) throws PermissionDeniedException {
return allDocs(broker, docs, recursive, null);
}
/**
* Retrieve all documents contained in this collections.
*
* If recursive is true, documents from sub-collections are
* included.
*
* @return The set of documents.
*/
public MutableDocumentSet allDocs(final DBBroker broker, final MutableDocumentSet docs, final boolean recursive, final LockedDocumentMap protectedDocs) throws PermissionDeniedException {
List<XmldbURI> subColls = null;
if(getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
try {
//Acquire a lock on the collection
getLock().acquire(Lock.READ_LOCK);
//Add all docs in this collection to the returned set
getDocuments(broker, docs);
//Get a list of sub-collection URIs. We will process them
//after unlocking this collection. otherwise we may deadlock ourselves
subColls = subCollections.keys();
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
} finally {
getLock().release(Lock.READ_LOCK);
}
}
if(recursive && subColls != null) {
// process the child collections
for(final XmldbURI childName : subColls) {
//TODO : resolve URI !
try {
final Collection child = broker.openCollection(path.appendInternal(childName), Lock.NO_LOCK);
//A collection may have been removed in the meantime, so check first
if(child != null) {
child.allDocs(broker, docs, recursive, protectedDocs);
}
} catch(final PermissionDeniedException pde) {
//SKIP to next collection
//TODO create an audit log??!
}
}
}
return docs;
}
public DocumentSet allDocs(final DBBroker broker, final MutableDocumentSet docs, final boolean recursive, final LockedDocumentMap lockMap, final int lockType) throws LockException, PermissionDeniedException {
XmldbURI uris[] = null;
if(getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
try {
//Acquire a lock on the collection
getLock().acquire(Lock.READ_LOCK);
//Add all documents in this collection to the returned set
getDocuments(broker, docs, lockMap, lockType);
//Get a list of sub-collection URIs. We will process them
//after unlocking this collection.
//otherwise we may deadlock ourselves
final List<XmldbURI> subColls = subCollections.keys();
if (subColls != null) {
uris = new XmldbURI[subColls.size()];
for(int i = 0; i < subColls.size(); i++) {
uris[i] = path.appendInternal(subColls.get(i));
}
}
} catch(final LockException e) {
LOG.error(e.getMessage());
throw e;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
if(recursive && uris != null) {
//Process the child collections
for(int i = 0; i < uris.length; i++) {
//TODO : resolve URI !
try {
final Collection child = broker.openCollection(uris[i], Lock.NO_LOCK);
// a collection may have been removed in the meantime, so check first
if(child != null) {
child.allDocs(broker, docs, recursive, lockMap, lockType);
}
} catch (final PermissionDeniedException pde) {
//SKIP to next collection
//TODO create an audit log??!
}
}
}
return docs;
}
/**
* Add all documents to the specified document set.
*
* @param docs
*/
public DocumentSet getDocuments(final DBBroker broker, final MutableDocumentSet docs) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
try {
getLock().acquire(Lock.READ_LOCK);
docs.addCollection(this);
addDocumentsToSet(broker, docs);
} catch(final LockException le) {
//TODO this should not be caught - it should be thrown - lock errors are bad!!!
LOG.error(le.getMessage(), le);
} finally {
getLock().release(Lock.READ_LOCK);
}
return docs;
}
public DocumentSet getDocumentsNoLock(final DBBroker broker, final MutableDocumentSet docs) {
docs.addCollection(this);
addDocumentsToSet(broker, docs);
return docs;
}
public DocumentSet getDocuments(final DBBroker broker, final MutableDocumentSet docs, final LockedDocumentMap lockMap, final int lockType) throws LockException, PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
try {
getLock().acquire(Lock.READ_LOCK);
docs.addCollection(this);
addDocumentsToSet(broker, docs, lockMap, lockType);
} finally {
getLock().release(Lock.READ_LOCK);
}
return docs;
}
private void addDocumentsToSet(final DBBroker broker, final MutableDocumentSet docs, final LockedDocumentMap lockMap, final int lockType) throws LockException {
for(final DocumentImpl doc : documents.values()) {
if(doc.getPermissions().validate(broker.getSubject(), Permission.WRITE)) {
doc.getUpdateLock().acquire(Lock.WRITE_LOCK);
docs.add(doc);
lockMap.add(doc);
}
}
}
private void addDocumentsToSet(final DBBroker broker, final MutableDocumentSet docs) {
for(final DocumentImpl doc : documents.values()) {
if(doc.getPermissions().validate(broker.getSubject(), Permission.READ)) {
docs.add(doc);
}
}
}
/*
private String[] getDocumentPaths() {
final String paths[] = new String[documents.size()];
int i = 0;
for (Iterator<String> iter = documents.keySet().iterator(); iter.hasNext(); i++) {
paths[i] = iter.next();
}
return paths;
}
*/
/**
* Check if this collection may be safely removed from the
* cache. Returns false if there are ongoing write operations,
* i.e. one or more of the documents is locked for write.
*
* @return A boolean value where true indicates it may be unloaded.
*/
@Override
public boolean allowUnload() {
if (getURI().startsWith(CollectionConfigurationManager.ROOT_COLLECTION_CONFIG_URI)) {
return false;
}
for(final DocumentImpl doc : documents.values()) {
if(doc.isLockedForWrite()) {
return false;
}
}
return true;
//try {
//lock.acquire(Lock.WRITE_LOCK);
//for (Iterator i = documents.values().iterator(); i.hasNext(); ) {
//DocumentImpl doc = (DocumentImpl) i.next();
//if (doc.isLockedForWrite())
//return false;
//}
//return true;
//} catch (LockException e) {
//LOG.warn("Failed to acquire lock on collection: " + getName(), e);
//} finally {
//lock.release();
//}
//return false;
}
@Override
public int compareTo(final Collection other) {
if(collectionId == other.collectionId) {
return Constants.EQUAL;
} else if(collectionId < other.collectionId) {
return Constants.INFERIOR;
} else {
return Constants.SUPERIOR;
}
}
@Override
public boolean equals(final Object obj) {
if(!(obj instanceof Collection)) {
return false;
}
return ((Collection) obj).collectionId == collectionId;
}
/**
* Returns the estimated amount of memory used by this collection
* and its documents. This information is required by the
* {@link org.exist.storage.CollectionCacheManager} to be able
* to resize the caches.
*
* @return estimated amount of memory in bytes
*/
public int getMemorySize() {
return SHALLOW_SIZE + documents.size() * DOCUMENT_SIZE;
}
/**
* Return the number of child-collections managed by this collection.
*
* @return The childCollectionCount value
*/
public int getChildCollectionCount(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
try {
getLock().acquire(Lock.READ_LOCK);
return subCollections.size();
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
return 0;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
/**
* Determines if this Collection has any documents, or sub-collections
*/
public boolean isEmpty(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
try {
getLock().acquire(Lock.READ_LOCK);
return documents.isEmpty() && subCollections.isEmpty();
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
return false;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
/**
* Get a child resource as identified by path. This method doesn't put
* a lock on the document nor does it recognize locks held by other threads.
* There's no guarantee that the document still exists when accessing it.
*
* @param broker
* @param path The name of the document (without collection path)
* @return the document
*/
public DocumentImpl getDocument(final DBBroker broker, final XmldbURI path) throws PermissionDeniedException {
try {
getLock().acquire(Lock.READ_LOCK);
final DocumentImpl doc = documents.get(path.getRawCollectionPath());
if(doc != null){
if(!doc.getPermissions().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read document: " + path.toString());
}
} else {
LOG.debug("Document " + path + " not found!");
}
return doc;
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
return null;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
/**
* Retrieve a child resource after putting a read lock on it. With this method,
* access to the received document object is safe.
*
* @deprecated Use getDocumentWithLock(DBBroker broker, XmldbURI uri, int lockMode)
* @param broker
* @param name
* @return The document that was locked.
* @throws LockException
*/
@Deprecated
public DocumentImpl getDocumentWithLock(final DBBroker broker, final XmldbURI name) throws LockException, PermissionDeniedException {
return getDocumentWithLock(broker,name,Lock.READ_LOCK);
}
/**
* Retrieve a child resource after putting a read lock on it. With this method,
* access to the received document object is safe.
*
* @param broker
* @param uri
* @param lockMode
* @return The document that was locked.
* @throws LockException
*/
public DocumentImpl getDocumentWithLock(final DBBroker broker, final XmldbURI uri, final int lockMode) throws LockException, PermissionDeniedException {
try {
getLock().acquire(Lock.READ_LOCK);
final DocumentImpl doc = documents.get(uri.getRawCollectionPath());
if(doc != null) {
if(!doc.getPermissions().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read document: " + uri.toString());
}
doc.getUpdateLock().acquire(lockMode);
}
return doc;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
public DocumentImpl getDocumentNoLock(final DBBroker broker, final String rawPath) throws PermissionDeniedException {
final DocumentImpl doc = documents.get(rawPath);
if(doc != null) {
if(!doc.getPermissions().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read document: " + rawPath);
}
}
return doc;
}
/**
* Release any locks held on the document.
* @deprecated Use releaseDocument(DocumentImpl doc, int mode)
* @param doc
*/
@Deprecated
public void releaseDocument(final DocumentImpl doc) {
if(doc != null) {
doc.getUpdateLock().release(Lock.READ_LOCK);
}
}
/**
* Release any locks held on the document.
*
* @param doc
*/
public void releaseDocument(final DocumentImpl doc, final int mode) {
if(doc != null) {
doc.getUpdateLock().release(mode);
}
}
/**
* Returns the number of documents in this collection.
*
* @return The documentCount value
*/
public int getDocumentCount(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
try {
getLock().acquire(Lock.READ_LOCK);
return documents.size();
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
return 0;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
public int getDocumentCountNoLock(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
return documents.size();
}
/**
* Get the internal id.
*
* @return The id value
*/
public int getId() {
return collectionId;
}
/**
* Get the name of this collection.
*
* @return The name value
*/
public XmldbURI getURI() {
return path;
}
/**
* Returns the parent-collection.
*
* @return The parent-collection or null if this is the root collection.
*/
public XmldbURI getParentURI() {
if(path.equals(XmldbURI.ROOT_COLLECTION_URI)) {
return null;
}
//TODO : resolve URI against ".." !
return path.removeLastSegment();
}
/**
* Gets the permissions attribute of the Collection object
*
* @return The permissions value
*/
final public Permission getPermissions() {
try {
getLock().acquire(Lock.READ_LOCK);
return permissions;
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
return permissions;
} finally {
getLock().release(Lock.READ_LOCK);
}
}
public Permission getPermissionsNoLock() {
return permissions;
}
/**
* Check if the collection has a child document.
*
* @param uri the name (without path) of the document
* @return A value of true when the collection has the document identified.
*/
public boolean hasDocument(final DBBroker broker, final XmldbURI uri) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
return documents.containsKey(uri.getRawCollectionPath());
}
/**
* Check if the collection has a sub-collection.
*
* @param name the name of the subcollection (without path).
* @return A value of true when the subcollection exists.
*/
public boolean hasSubcollection(final DBBroker broker, final XmldbURI name) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
try {
getLock().acquire(Lock.READ_LOCK);
return subCollections.contains(name);
} catch(final LockException e) {
LOG.warn(e.getMessage(), e);
//TODO : ouch ! Should we return at any price ? Xithout even logging ? -pb
return subCollections.contains(name);
} finally {
getLock().release(Lock.READ_LOCK);
}
}
public boolean hasSubcollectionNoLock(final DBBroker broker, final XmldbURI name) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
return subCollections.contains(name);
}
/**
* Returns an iterator on the child-documents in this collection.
*
* @return A iterator of all the documents in the collection.
*/
public Iterator<DocumentImpl> iterator(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
return getDocuments(broker, new DefaultDocumentSet()).getDocumentIterator();
}
public Iterator<DocumentImpl> iteratorNoLock(final DBBroker broker) throws PermissionDeniedException {
if(!getPermissionsNoLock().validate(broker.getSubject(), Permission.READ)) {
throw new PermissionDeniedException("Permission denied to read collection: " + path);
}
return getDocumentsNoLock(broker, new DefaultDocumentSet()).getDocumentIterator();
}
/**
* Write collection contents to stream.
*
* @param ostream
* @throws IOException
*/
public void write(final DBBroker broker, final VariableByteOutputStream ostream) throws IOException {
ostream.writeInt(collectionId);
ostream.writeInt(subCollections.size());
for(final Iterator<XmldbURI> i = subCollections.iterator(); i.hasNext(); ) {
final XmldbURI childCollectionURI = i.next();
ostream.writeUTF(childCollectionURI.toString());
}
permissions.write(ostream);
ostream.writeLong(created);
}
public interface InternalAccess {
public void addDocument(DocumentImpl doc) throws EXistException;
public int getId();
}