• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.95
/exist-core/src/main/java/org/exist/dom/persistent/DocumentImpl.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.dom.persistent;
50

51
import com.evolvedbinary.j8fu.tuple.Tuple2;
52
import net.jcip.annotations.NotThreadSafe;
53
import org.exist.EXistException;
54
import org.exist.Resource;
55
import org.exist.collections.Collection;
56
import org.exist.collections.LockedCollection;
57
import org.exist.dom.QName;
58
import org.exist.dom.QName.IllegalQNameException;
59
import org.exist.dom.memtree.DocumentFragmentImpl;
60
import org.exist.numbering.NodeId;
61
import org.exist.security.SecurityManager;
62
import org.exist.security.*;
63
import org.exist.storage.*;
64
import org.exist.storage.io.VariableByteInput;
65
import org.exist.storage.io.VariableByteOutputStream;
66
import org.exist.storage.lock.EnsureContainerLocked;
67
import org.exist.storage.lock.EnsureLocked;
68
import org.exist.storage.txn.Txn;
69
import org.exist.util.MimeType;
70
import org.exist.util.XMLString;
71
import org.exist.xmldb.XmldbURI;
72
import org.exist.xquery.Constants;
73
import org.exist.xquery.Expression;
74
import org.exist.xquery.NameTest;
75
import org.exist.xquery.value.Type;
76
import org.w3c.dom.*;
77

78
import javax.annotation.Nullable;
79
import javax.xml.XMLConstants;
80
import java.io.IOException;
81
import java.util.Optional;
82

83
import static java.nio.charset.StandardCharsets.UTF_8;
84
import static org.exist.dom.QName.Validity.ILLEGAL_FORMAT;
85
import static org.exist.storage.lock.Lock.LockMode.READ_LOCK;
86
import static org.exist.storage.lock.Lock.LockMode.WRITE_LOCK;
87

88
/**
89
 * Represents a persistent document object in the database;
90
 * it can be an XML_FILE , a BINARY_FILE, or Xquery source code.
91
 *
92
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
93
 */
94
@NotThreadSafe
95
public class DocumentImpl extends NodeImpl<DocumentImpl> implements Resource, Document {
96

97
    public static final int UNKNOWN_DOCUMENT_ID = -1;
98

99
    public static final byte XML_FILE = 0;
100
    public static final byte BINARY_FILE = 1;
101

102
    public static final int LENGTH_DOCUMENT_ID = 4; //sizeof int
103
    public static final int LENGTH_DOCUMENT_TYPE = 1; //sizeof byte
104

105
    //public static final byte DOCUMENT_NODE_SIGNATURE = 0x0F;
106

107
    public static final byte NO_XMLDECL = 0;
108
    public static final byte HAS_XMLDECL = 1;
109

110
    public static final byte NO_DOCTYPE = 0;
111
    public static final byte HAS_DOCTYPE = 1;
112

113
    public static final byte NO_LOCKTOKEN = 0;
114
    public static final byte HAS_LOCKTOKEN = 2;
115

116
    protected final BrokerPool pool;
117

118
    /**
119
     * number of child nodes
120
     */
121
    private int children = 0;
1✔
122
    private long[] childAddress = null;
1✔
123

124
    /**
125
     * the collection this document belongs to
126
     */
127
    private transient Collection collection = null;
1✔
128

129
    /**
130
     * the document's id
131
     */
132
    private final int docId;
133

134
    /**
135
     * Just the document's file name
136
     */
137
    private XmldbURI fileURI = null;
1✔
138

139
    /**
140
     * Lazily computed. Needs to be recomputed if {@link #fileURI} or {@link #collection} change
141
     */
142
    private XmldbURI uri = null;
1✔
143

144
    private Permission permissions = null;
1✔
145

146
    private DocumentMetadata metadata = null;
1✔
147

148
    /**
149
     * The mimeType of the document
150
     */
151
    protected String mimeType = MimeType.XML_TYPE.getName();
1✔
152

153
    /**
154
     * The creation time of this document
155
     */
156
    protected long created = 0;
1✔
157

158
    /**
159
     * Time of the last modification
160
     */
161
    protected long lastModified = 0;
1✔
162

163
    /**
164
     * The number of data pages occupied by this document
165
     */
166
    protected int pageCount = 0;
1✔
167

168
    /**
169
     * Contains the user id if a user lock is held on this resource
170
     */
171
    protected int userLock = 0;
1✔
172

173
    /**
174
     * The document's XML Declaration - if specified.
175
     */
176
    @Nullable private XMLDeclarationImpl xmlDecl = null;
1✔
177

178
    /**
179
     * The document's doctype declaration - if specified.
180
     */
181
    protected DocumentType docType = null;
1✔
182

183
    /**
184
     * Associated lock token - if available
185
     */
186
    protected LockToken lockToken = null;
1✔
187

188
    protected transient int splitCount = 0;
1✔
189

190
    private boolean isReferenced = false;
1✔
191

192
    /**
193
     * Creates a new <code>DocumentImpl</code> instance.
194
     *
195
     * Package private - for testing!
196
     *
197
     * @param pool a <code>BrokerPool</code> instance representing the db
198
     */
199
    DocumentImpl(final BrokerPool pool, final int docId) {
200
        this(null, pool, docId);
×
201
    }
×
202

203
    /**
204
     * Creates a new <code>DocumentImpl</code> instance.
205
     *
206
     * Package private - for testing!
207
     *
208
     * @param expression the expression from which this document derives
209
     * @param pool a <code>BrokerPool</code> instance representing the db
210
     */
211
    DocumentImpl(final Expression expression, final BrokerPool pool, final int docId) {
212
        this(expression, pool, null, docId, null);
1✔
213
    }
1✔
214

215
    /**
216
     * Creates a new persistent Document instance.
217
     *
218
     * @param pool The broker pool
219
     * @param collection The Collection which holds this document
220
     * @param docId the id of the document
221
     * @param fileURI The name of the document
222
     */
223
    public DocumentImpl(final BrokerPool pool, @Nullable final Collection collection, final int docId, @Nullable
224
            final XmldbURI fileURI) {
225
        this(null, pool, collection, docId, fileURI);
×
226
    }
×
227

228
    /**
229
     * Creates a new persistent Document instance.
230
     *
231
     * @param expression the expression from which this document derives
232
     * @param pool The broker pool
233
     * @param collection The Collection which holds this document
234
     * @param docId the id of the document
235
     * @param fileURI The name of the document
236
     */
237
    public DocumentImpl(final Expression expression, final BrokerPool pool, @Nullable final Collection collection, final int docId, @Nullable
238
            final XmldbURI fileURI) {
239
        this(expression, pool, collection, docId, fileURI,
1✔
240
                PermissionFactory.getDefaultResourcePermission(pool.getSecurityManager()), 0, null,
1✔
241
                System.currentTimeMillis(), null, null, null, null);
1✔
242
    }
1✔
243

244
    /**
245
     * Creates a new persistent Document instance to replace an existing document instance.
246
     *
247
     * @param docId the id of the document
248
     * @param prevDoc The previous Document object that we are overwriting
249
     */
250
    public DocumentImpl(final int docId, final DocumentImpl prevDoc) {
251
        this(null, docId, prevDoc);
×
252
    }
×
253

254
    /**
255
     * Creates a new persistent Document instance to replace an existing document instance.
256
     *
257
     * @param expression the expression from which the Document object derives
258
     * @param docId the id of the document
259
     * @param prevDoc The previous Document object that we are overwriting
260
     */
261
    public DocumentImpl(final Expression expression, final int docId, final DocumentImpl prevDoc) {
262
        this(expression, prevDoc.pool, prevDoc.collection, docId, prevDoc.fileURI, prevDoc.permissions.copy(), 0, null,
1✔
263
                System.currentTimeMillis(), null, null, null, null);
1✔
264
    }
1✔
265

266
    /**
267
     * Creates a new persistent Document instance to replace an existing document instance.
268
     *
269
     * @param pool The broker pool
270
     * @param collection The Collection which holds this document
271
     * @param docId the id of the document
272
     * @param fileURI The name of the document
273
     * @param permissions the permissions of the document
274
     * @param children the number of children that the document has
275
     * @param childAddress the addresses of the child nodes
276
     * @param created the created time of the document
277
     * @param lastModified the last modified time of the document, or null to use the {@code created} time
278
     * @param mimeType the media type of the document, or null for application/xml
279
     * @param docType the document type, or null
280
     *
281
     * @deprecated Use {@link DocumentImpl(BrokerPool, Collection, int, XmldbURI, Permission, int, long[], long, Long, String, XMLDeclarationImpl, DocumentType)}
282
     */
283
    @Deprecated
284
    public DocumentImpl(final BrokerPool pool, @Nullable final Collection collection,
285
                        final int docId, final XmldbURI fileURI, final Permission permissions,
286
                        final int children, @Nullable final long[] childAddress,
287
                        final long created, @Nullable final Long lastModified, @Nullable final String mimeType,
288
                        @Nullable final DocumentType docType) {
289
        this(null, pool, collection, docId, fileURI, permissions, children, childAddress, created, lastModified, mimeType, null, docType);
×
290
    }
×
291

292
    /**
293
     * Creates a new persistent Document instance to replace an existing document instance.
294
     *
295
     * @param pool The broker pool
296
     * @param collection The Collection which holds this document
297
     * @param docId the id of the document
298
     * @param fileURI The name of the document
299
     * @param permissions the permissions of the document
300
     * @param children the number of children that the document has
301
     * @param childAddress the addresses of the child nodes
302
     * @param created the created time of the document
303
     * @param lastModified the last modified time of the document, or null to use the {@code created} time
304
     * @param mimeType the media type of the document, or null for application/xml
305
     * @param xmlDecl the XML Declaration, or null
306
     * @param docType the document type, or null
307
     */
308
    public DocumentImpl(final BrokerPool pool, @Nullable final Collection collection,
309
            final int docId, final XmldbURI fileURI, final Permission permissions,
310
            final int children, @Nullable final long[] childAddress,
311
            final long created, @Nullable final Long lastModified, @Nullable final String mimeType,
312
            @Nullable final XMLDeclarationImpl xmlDecl, @Nullable final DocumentType docType) {
313
        this(null, pool, collection, docId, fileURI, permissions, children, childAddress, created, lastModified, mimeType, xmlDecl, docType);
×
314
    }
×
315

316
    /**
317
     * Creates a new persistent Document instance to replace an existing document instance.
318
     *
319
     * @param expression the expression from which the document instance derives
320
     * @param pool The broker pool
321
     * @param collection The Collection which holds this document
322
     * @param docId the id of the document
323
     * @param fileURI The name of the document
324
     * @param permissions the permissions of the document
325
     * @param children the number of children that the document has
326
     * @param childAddress the addresses of the child nodes
327
     * @param created the created time of the document
328
     * @param lastModified the last modified time of the document, or null to use the {@code created} time
329
     * @param mimeType the media type of the document, or null for application/xml
330
     * @param docType the document type, or null
331
     *
332
     * @deprecated Use {@link DocumentImpl(Expression, BrokerPool, Collection, int ,XmldbURI, Permission, int, long[], long, Long, String, XMLDeclarationImpl, DocumentType)}
333
     */
334
    @Deprecated
335
    public DocumentImpl(final Expression expression, final BrokerPool pool, @Nullable final Collection collection,
336
            final int docId, final XmldbURI fileURI, final Permission permissions,
337
            final int children, @Nullable final long[] childAddress,
338
            final long created, @Nullable final Long lastModified, @Nullable final String mimeType,
339
            @Nullable final DocumentType docType) {
340
        this(expression, pool, collection, docId, fileURI, permissions, children, childAddress, created, lastModified, mimeType, null, docType);
1✔
341
    }
1✔
342

343
    /**
344
     * Creates a new persistent Document instance to replace an existing document instance.
345
     *
346
     * @param expression the expression from which the document instance derives
347
     * @param pool The broker pool
348
     * @param collection The Collection which holds this document
349
     * @param docId the id of the document
350
     * @param fileURI The name of the document
351
     * @param permissions the permissions of the document
352
     * @param children the number of children that the document has
353
     * @param childAddress the addresses of the child nodes
354
     * @param created the created time of the document
355
     * @param lastModified the last modified time of the document, or null to use the {@code created} time
356
     * @param mimeType the media type of the document, or null for application/xml
357
     * @param xmlDecl the XML Declaration, or null
358
     * @param docType the document type, or null
359
     */
360
    public DocumentImpl(final Expression expression, final BrokerPool pool, @Nullable final Collection collection,
361
            final int docId, final XmldbURI fileURI, final Permission permissions,
362
            final int children, @Nullable final long[] childAddress,
363
            final long created, @Nullable final Long lastModified, @Nullable final String mimeType,
364
            @Nullable final XMLDeclarationImpl xmlDecl, @Nullable final DocumentType docType) {
365
        super(expression);
1✔
366
        this.pool = pool;
1✔
367

368
        // NOTE: We must not keep a reference to a LockedCollection in the Document object!
369
        this.collection = LockedCollection.unwrapLocked(collection);
1✔
370

371
        this.docId = docId;
1✔
372
        this.fileURI = fileURI;
1✔
373
        this.permissions = permissions;
1✔
374
        this.children = children;
1✔
375
        this.childAddress = childAddress;
1✔
376
        this.created = created;
1✔
377
        this.lastModified = lastModified == null ? created : lastModified;
1✔
378
        this.mimeType = mimeType == null ?  MimeType.XML_TYPE.getName() : mimeType;
1✔
379
        this.xmlDecl = xmlDecl;
1✔
380
        this.docType = docType;
1✔
381

382
        //inherit the group to the resource if current collection is setGid
383
        if(collection != null && collection.getPermissions().isSetGid()) {
1✔
384
            try {
385
                this.permissions.setGroupFrom(collection.getPermissions());
1✔
386
            } catch(final PermissionDeniedException pde) {
1✔
387
                throw new IllegalArgumentException(pde); //TODO improve
×
388
            }
389
        }
390
    }
1✔
391

392
    //TODO document really should not hold a reference to the brokerpool
393
    public BrokerPool getBrokerPool() {
394
        return pool;
1✔
395
    }
396

397
    /************************************************
398
     *
399
     * Document metadata
400
     *
401
     ************************************************/
402

403
    /**
404
     * The method <code>getCollection</code>
405
     *
406
     * @return a <code>Collection</code> value
407
     */
408
    @EnsureContainerLocked(mode=READ_LOCK)
409
    public Collection getCollection() {
410
        return collection;
1✔
411
    }
412

413
    /**
414
     * Set the Collection for the document
415
     *
416
     * @param collection The Collection that the document belongs too
417
     */
418
    @EnsureContainerLocked(mode=WRITE_LOCK)
419
    public void setCollection(final Collection collection) {
420
        this.collection = collection;
1✔
421
        this.uri = null;  // reset
1✔
422
    }
1✔
423

424
    /**
425
     * The method <code>getDocId</code>
426
     *
427
     * @return an <code>int</code> value
428
     */
429
    @EnsureContainerLocked(mode=READ_LOCK)
430
    public int getDocId() {
431
        return docId;
1✔
432
    }
433

434
    /**
435
     * @return the type of this resource, either  {@link #XML_FILE} or
436
     * {@link #BINARY_FILE}.
437
     */
438
    public byte getResourceType() {
439
        return XML_FILE;
1✔
440
    }
441

442
    /**
443
     * The method <code>getFileURI</code>
444
     *
445
     * @return a <code>XmldbURI</code> value
446
     */
447
    //@EnsureContainerLocked(mode=READ_LOCK)  // TODO(AR) temporarily we need to allow some unlocked access
448
    public XmldbURI getFileURI() {
449
        return fileURI;
1✔
450
    }
451

452
    /**
453
     * The method <code>setFileURI</code>
454
     *
455
     * @param fileURI a <code>XmldbURI</code> value
456
     */
457
    @EnsureContainerLocked(mode=WRITE_LOCK)
458
    public void setFileURI(final XmldbURI fileURI) {
459
        this.fileURI = fileURI;
1✔
460
        this.uri = null;  // reset
1✔
461
    }
1✔
462

463
    //@EnsureContainerLocked(mode=READ_LOCK)  // TODO(AR) temporarily we need to allow some unlocked access
464
    @Override
465
    public XmldbURI getURI() {
466
        if (uri == null) {
1✔
467
            if (collection == null) {
1!
468
                uri = (XmldbURI) fileURI.clone();
×
469
            } else {
×
470
                uri = collection.getURI().append(fileURI);
1✔
471
            }
472
        }
473
        return uri;
1✔
474
    }
475

476
    public long getCreated() {
477
        return created;
1✔
478
    }
479

480
    public void setCreated(final long created) {
481
        this.created = created;
1✔
482
        if (lastModified == 0) {
1!
483
            this.lastModified = created;
×
484
        }
485
    }
1✔
486

487
    public long getLastModified() {
488
        return lastModified;
1✔
489
    }
490

491
    public void setLastModified(final long lastModified) {
492
        this.lastModified = lastModified;
1✔
493
    }
1✔
494

495
    public String getMimeType() {
496
        return mimeType;
1✔
497
    }
498

499
    public void setMimeType(final String mimeType) {
500
        this.mimeType = mimeType;
1✔
501
    }
1✔
502

503
    /**
504
     * Get the number of pages occupied by this document.
505
     *
506
     * @return the number of pages currently occupied by this document.
507
     */
508
    public int getPageCount() {
509
        return pageCount;
1✔
510
    }
511

512
    /**
513
     * Set the number of pages currently occupied by this document.
514
     *
515
     * @param pageCount number of pages currently occupied by this document
516
     *
517
     */
518
    public void setPageCount(final int pageCount) {
519
        this.pageCount = pageCount;
1✔
520
    }
1✔
521

522
    public void incPageCount() {
523
        ++pageCount;
1✔
524
    }
1✔
525

526
    public void decPageCount() {
527
        --pageCount;
1✔
528
    }
1✔
529

530
    public void setUserLock(final int userLock) {
531
        this.userLock = userLock;
×
532
    }
×
533

534
    /**
535
     * @deprecated Will be removed when org.exist.dom.persistent.DocumentMetadata is removed
536
     * @return the internal user lock number
537
     */
538
    @Deprecated
539
    int getUserLockInternal() {
540
        return userLock;
×
541
    }
542

543
    public LockToken getLockToken() {
544
        return lockToken;
1✔
545
    }
546

547
    public void setLockToken(final LockToken token) {
548
        lockToken = token;
×
549
    }
×
550

551
    /**
552
     * @deprecated use {@link #getDoctype()}.
553
     */
554
    @Deprecated
555
    public DocumentType getDocType() {
556
        return docType;
×
557
    }
558

559
    public void setDocType(final DocumentType docType) {
560
        this.docType = docType;
×
561
    }
×
562

563
    /**
564
     * Increase the page split count of this document. The number
565
     * of pages that have been split during inserts serves as an
566
     * indicator for the fragmentation
567
     */
568
    public void incSplitCount() {
569
        splitCount++;
1✔
570
    }
1✔
571

572
    public int getSplitCount() {
573
        return splitCount;
1✔
574
    }
575

576
    public void setSplitCount(final int count) {
577
        splitCount = count;
1✔
578
    }
1✔
579

580
    public boolean isReferenced() {
581
        return isReferenced;
1✔
582
    }
583

584
    public void setReferenced(final boolean referenced) {
585
        isReferenced = referenced;
×
586
    }
×
587

588
    @Override
589
    @EnsureContainerLocked(mode=READ_LOCK)
590
    public Permission getPermissions() {
591
        return permissions;
1✔
592
    }
593

594
    /**
595
     * The method <code>setMode</code>
596
     *
597
     * @param perm a <code>Permission</code> value
598
     * @deprecated This function is considered a security problem
599
     * and should be removed, move code to copyOf or Constructor
600
     */
601
    @Deprecated
602
    @EnsureContainerLocked(mode=WRITE_LOCK)
603
    public void setPermissions(final Permission perm) {
604
        permissions = perm;
1✔
605
    }
1✔
606

607
    /**
608
     * The method <code>setMetadata</code>
609
     *
610
     * @param meta a <code>DocumentMetadata</code> value
611
     * @deprecated This function is considered a security problem
612
     * and should be removed, move code to copyOf or Constructor
613
     */
614
    @Deprecated
615
    @EnsureContainerLocked(mode=WRITE_LOCK)
616
    public void setMetadata(final DocumentMetadata meta) {
617
        throw new UnsupportedOperationException();
×
618
    }
619

620
    /**
621
     * Get the metadata of the Document
622
     *
623
     * @return The Document metadata
624
     *
625
     * @deprecated Will be removed in eXist-db 6.0.0. Instead use the direct methods on this class.
626
     */
627
    @Deprecated
628
    @EnsureContainerLocked(mode=READ_LOCK)
629
    public DocumentMetadata getMetadata() {
630
        if (metadata == null) {
1!
631
            metadata = new DocumentMetadata(this);
1✔
632
        }
633
        return metadata;
1✔
634
    }
635

636
    /************************************************
637
     *
638
     * Persistent node methods
639
     *
640
     ************************************************/
641

642
    /**
643
     * Copy the relevant internal fields from the specified document object.
644
     * This is called by {@link Collection} when replacing a document.
645
     *
646
     * @param broker the DBBroker
647
     * @param other    a <code>DocumentImpl</code> value
648
     * @param prev if there was an existing document which we are replacing,
649
     *     we will copy the mode, ACL, and birth time from the existing document.
650
     * @throws PermissionDeniedException in case user has not sufficient privileges
651
     */
652
    public void copyOf(final DBBroker broker, final DocumentImpl other, @EnsureLocked(mode=READ_LOCK) @Nullable final DocumentImpl prev) throws PermissionDeniedException {
653
        copyOf(broker, other, prev == null ? null : new Tuple2<>(prev.getPermissions(), prev.getCreated()));
1✔
654
    }
1✔
655

656
    /**
657
     * Copy the relevant internal fields from the specified document object.
658
     * This is called by {@link Collection} when replacing a document.
659
     * @param broker the DBBroker
660
     * @param other a <code>DocumentImpl</code> value
661
     * @param prev if there was an existing document which we are replacing,
662
     *     we will copy the mode, ACL, and birth time from the existing document.
663
     * @throws PermissionDeniedException in case user has not sufficient privileges
664
     */
665
    public void copyOf(final DBBroker broker, final DocumentImpl other, @Nullable final Collection.CollectionEntry prev) throws PermissionDeniedException {
666
        copyOf(broker, other, prev == null ? null : new Tuple2<>(prev.getPermissions(), prev.getCreated()));
×
667
    }
×
668

669
    /**
670
     * Copy the relevant internal fields from the specified document object.
671
     * This is called by {@link Collection} when replacing a document.
672
     *
673
     * @param other    a <code>DocumentImpl</code> value
674
     * @param prev A tuple, containing the permissions and birth time of any
675
     *     previous document that we are replacing; We will copy the mode, ACL,
676
     *     and birth time from the existing document.
677
     */
678
    @EnsureContainerLocked(mode=WRITE_LOCK)
679
    private void copyOf(final DBBroker broker, @EnsureLocked(mode=READ_LOCK) final DocumentImpl other, @Nullable final Tuple2<Permission, Long> prev) throws PermissionDeniedException {
680
        this.childAddress = null;
1✔
681
        this.children = 0;
1✔
682

683
        this.created = other.created;
1✔
684
        this.lastModified = other.lastModified;
1✔
685
        this.mimeType = other.mimeType;
1✔
686
        this.docType = other.docType;
1✔
687

688
        final long timestamp = System.currentTimeMillis();
1✔
689
        if(prev != null) {
1✔
690
            // replaced file should have same owner user:group as prev file
691
            if (permissions.getGroup().getId() != prev._1.getGroup().getId()) {
1!
692
                permissions.setGroup(prev.get_1().getGroup());
×
693
            }
694
            if (permissions.getOwner().getId() != prev._1.getOwner().getId()) {
1!
695
                permissions.setOwner(prev.get_1().getOwner());
×
696
            }
697

698
            //copy mode and acl from prev file
699
            copyModeAcl(broker, prev._1, permissions);
1✔
700

701
            // set birth time to same as prev file
702
            this.created = prev._2;
1✔
703

704
        } else {
1✔
705
            // copy mode and acl from source file
706
            copyModeAcl(broker, other.getPermissions(), permissions);
1✔
707

708
            // set birth time to the current timestamp
709
            this.created = timestamp;
1✔
710
        }
711

712
        // always set mtime
713
        this.lastModified = timestamp;
1✔
714

715
        // reset pageCount: will be updated during storage
716
        this.pageCount = 0;
1✔
717
    }
1✔
718

719
    private void copyModeAcl(final DBBroker broker, final Permission srcPermissions, final Permission destPermissions) throws PermissionDeniedException {
720
        PermissionFactory.chmod(broker, destPermissions, Optional.of(srcPermissions.getMode()), Optional.empty());
1✔
721

722
        if (srcPermissions instanceof SimpleACLPermission srcAclPermissions && destPermissions instanceof SimpleACLPermission destAclPermissions) {
1!
723
            if (!destAclPermissions.equalsAcl(srcAclPermissions)) {
1!
724
                PermissionFactory.chacl(destAclPermissions, newAcl ->
×
725
                    ((SimpleACLPermission)newAcl).copyAclOf(srcAclPermissions)
×
726
                );
727
            }
728
        }
729
    }
1✔
730

731
    /**
732
     * The method <code>copyChildren</code>
733
     *
734
     * @param other a <code>DocumentImpl</code> value
735
     */
736
    @EnsureContainerLocked(mode=WRITE_LOCK)
737
    public void copyChildren(@EnsureLocked(mode=READ_LOCK) final DocumentImpl other) {
738
        childAddress = other.childAddress;
1✔
739
        children = other.children;
1✔
740
    }
1✔
741

742
    /**
743
     * The method <code>setUserLock</code>
744
     *
745
     * @param user an <code>User</code> value
746
     */
747
    @EnsureContainerLocked(mode=WRITE_LOCK)
748
    public void setUserLock(final Account user) {
749
        this.userLock = (user == null ? 0 : user.getId());
1✔
750
    }
1✔
751

752
    /**
753
     * The method <code>getUserLock</code>
754
     *
755
     * @return an <code>User</code> value
756
     */
757
    @EnsureContainerLocked(mode=READ_LOCK)
758
    public Account getUserLock() {
759
        if (userLock == 0) {
1✔
760
            return null;
1✔
761
        }
762
        final SecurityManager secman = pool.getSecurityManager();
1✔
763
        return secman.getAccount(userLock);
1✔
764
    }
765

766
    /**
767
     * Returns the estimated size of the data in this document.
768
     *
769
     * As an estimation, the number of pages occupied by the document
770
     * is multiplied with the current page size.
771
     * @return the estimated size of the data in this document.
772
     *
773
     */
774
    @EnsureContainerLocked(mode=READ_LOCK)
775
    public long getContentLength() {
776
        final long length = pageCount * pool.getPageSize();
1✔
777
        return (length < 0) ? 0 : length;
1!
778
    }
779

780
    /**
781
     * The method <code>triggerDefrag</code>
782
     */
783
    public void triggerDefrag() {
784
        int fragmentationLimit = -1;
×
785
        final Object property = pool.getConfiguration().getProperty(DBBroker.PROPERTY_XUPDATE_FRAGMENTATION_FACTOR);
×
786
        if(property != null) {
×
787
            fragmentationLimit = (Integer) property;
×
788
        }
789
        if(fragmentationLimit != -1) {
×
790
            this.splitCount = fragmentationLimit;
×
791
        }
792
    }
×
793

794
    /**
795
     * The method <code>getNode</code>
796
     *
797
     * @param nodeId a <code>NodeId</code> value
798
     * @return a <code>Node</code> value
799
     */
800
    public Node getNode(final NodeId nodeId) {
801
        try(final DBBroker broker = pool.getBroker()) {
1✔
802
            return broker.objectWith(this, nodeId);
1✔
803
        } catch(final EXistException e) {
×
804
            LOG.warn("Error occurred while retrieving node: {}", e.getMessage(), e);
×
805
        }
806
        return null;
×
807
    }
808

809
    /**
810
     * The method <code>getNode</code>
811
     *
812
     * @param p a <code>NodeProxy</code> value
813
     * @return a <code>Node</code> value
814
     */
815
    public Node getNode(final NodeProxy p) {
816
        if(p.getNodeId().getTreeLevel() == 1) {
1✔
817
            return getDocumentElement();
1✔
818
        }
819
        try(final DBBroker broker = pool.getBroker()) {
1✔
820
            return broker.objectWith(p);
1✔
821
        } catch(final Exception e) {
×
822
            LOG.warn("Error occurred while retrieving node: {}", e.getMessage(), e);
×
823
        }
824
        return null;
×
825
    }
826

827
    /**
828
     * The method <code>resizeChildList</code>
829
     */
830
    private void resizeChildList() {
831
        final long[] newChildList = new long[children];
1✔
832
        if(childAddress != null) {
1✔
833
            System.arraycopy(childAddress, 0, newChildList, 0, childAddress.length);
1✔
834
        }
835
        childAddress = newChildList;
1✔
836
    }
1✔
837

838
    /**
839
     * The method <code>appendChild</code>
840
     *
841
     * @param child a <code>NodeHandle</code> value
842
     * @throws DOMException if an error occurs
843
     */
844
    @EnsureContainerLocked(mode=WRITE_LOCK)
845
    public void appendChild(final NodeHandle child) throws DOMException {
846
        ++children;
1✔
847
        resizeChildList();
1✔
848
        childAddress[children - 1] = child.getInternalAddress();
1✔
849
    }
1✔
850

851
    /**
852
     * The method <code>write</code>
853
     *
854
     * @param ostream a <code>VariableByteOutputStream</code> value
855
     * @throws IOException if an error occurs
856
     */
857
    @EnsureContainerLocked(mode=READ_LOCK)
858
    public void write(final VariableByteOutputStream ostream) throws IOException {
859
        try {
860
            ostream.writeInt(docId);
1✔
861
            ostream.writeUTF(fileURI.toString());
1✔
862
            getPermissions().write(ostream);
1✔
863
            ostream.writeInt(children);
1✔
864
            if(children > 0) {
1!
865
                for(int i = 0; i < children; i++) {
1✔
866
                    ostream.writeInt(StorageAddress.pageFromPointer(childAddress[i]));
1✔
867
                    ostream.writeShort(StorageAddress.tidFromPointer(childAddress[i]));
1✔
868
                }
869
            }
870

871
            // document attributes
872
            writeDocumentAttributes(pool.getSymbols(), ostream);
1✔
873

874
        } catch(final IOException e) {
1✔
875
            LOG.warn("io error while writing document data", e);
×
876
            //TODO : raise exception ?
877
        }
878
    }
1✔
879

880
    void writeDocumentAttributes(final SymbolTable symbolTable, final VariableByteOutputStream ostream) throws IOException {
881
        ostream.writeLong(created);
1✔
882
        ostream.writeLong(lastModified);
1✔
883
        ostream.writeInt(symbolTable.getMimeTypeId(mimeType));
1✔
884
        ostream.writeInt(pageCount);
1✔
885
        ostream.writeInt(userLock);
1✔
886
        if (xmlDecl != null) {
1✔
887
            ostream.writeByte(HAS_XMLDECL);
1✔
888
            xmlDecl.write(ostream);
1✔
889
        } else {
1✔
890
            ostream.writeByte(NO_XMLDECL);
1✔
891
        }
892
        if (docType != null) {
1✔
893
            ostream.writeByte(HAS_DOCTYPE);
1✔
894
            ((DocumentTypeImpl) docType).write(ostream);
1✔
895
        } else {
1✔
896
            ostream.writeByte(NO_DOCTYPE);
1✔
897
        }
898
        if (lockToken != null) {
1!
899
            ostream.writeByte(HAS_LOCKTOKEN);
×
900
            lockToken.write(ostream);
×
901
        } else {
×
902
            ostream.writeByte(NO_LOCKTOKEN);
1✔
903
        }
904
    }
1✔
905

906
    /**
907
     * Deserialize the document object from bytes.
908
     *
909
     * @param pool the database
910
     * @param istream the byte stream to read
911
     *
912
     * @return the document object.
913
     *
914
     * @throws IOException  if an error occurs
915
     */
916
    public static DocumentImpl read(final BrokerPool pool, final VariableByteInput istream) throws IOException {
917
        final int docId = istream.readInt();
1✔
918
        final XmldbURI fileURI = XmldbURI.createInternal(istream.readUTF());
1✔
919
        final Permission permissions = PermissionFactory.getDefaultResourcePermission(pool.getSecurityManager());
1✔
920
        permissions.read(istream);
1✔
921

922
        final int children = istream.readInt();
1✔
923
        final long childAddress[] = new long[children];
1✔
924
        for (int i = 0; i < children; i++) {
1✔
925
            childAddress[i] = StorageAddress.createPointer(istream.readInt(), istream.readShort());
1✔
926
        }
927

928
        // load document attributes
929
        final long created = istream.readLong();
1✔
930
        final long lastModified = istream.readLong();
1✔
931
        final int mimeTypeSymbolsIndex = istream.readInt();
1✔
932
        final String mimeType = pool.getSymbols().getMimeType(mimeTypeSymbolsIndex);
1✔
933
        final int pageCount = istream.readInt();
1✔
934
        final int userLock = istream.readInt();
1✔
935
        final XMLDeclarationImpl xmlDecl;
936
        if (istream.readByte() == HAS_XMLDECL) {
1✔
937
            xmlDecl = XMLDeclarationImpl.read(istream);
1✔
938
        } else {
1✔
939
            xmlDecl = null;
1✔
940
        }
941
        final DocumentTypeImpl docType;
942
        if (istream.readByte() == HAS_DOCTYPE) {
1✔
943
            docType = DocumentTypeImpl.read(istream);
1✔
944
        } else {
1✔
945
            docType = null;
1✔
946
        }
947
        final LockToken lockToken;
948
        if (istream.readByte() == HAS_LOCKTOKEN) {
1!
949
            lockToken = LockToken.read(istream);
×
950
        } else {
×
951
            lockToken = null;
1✔
952
        }
953

954
        final DocumentImpl doc = new DocumentImpl(null, pool, null, docId, fileURI, permissions, children, childAddress, created, lastModified, mimeType, xmlDecl, docType);
1✔
955
        doc.pageCount = pageCount;
1✔
956
        doc.userLock = userLock;
1✔
957
        doc.lockToken = lockToken;
1✔
958
        return doc;
1✔
959
    }
960

961
    /**
962
     * The method <code>compareTo</code>
963
     *
964
     * @param other an <code>DocumentImpl</code> value
965
     * @return an <code>int</code> value
966
     */
967
    @Override
968
    @EnsureContainerLocked(mode=READ_LOCK)
969
    public int compareTo(@EnsureLocked(mode=READ_LOCK) final DocumentImpl other) {
970
        final long otherId = other.docId;
×
971
        if(otherId == docId) {
×
972
            return Constants.EQUAL;
×
973
        } else if(docId < otherId) {
×
974
            return Constants.INFERIOR;
×
975
        } else {
976
            return Constants.SUPERIOR;
×
977
        }
978
    }
979

980
    @Override
981
    public IStoredNode updateChild(final Txn transaction, final Node oldChild, final Node newChild) throws DOMException {
982
        if(!(oldChild instanceof StoredNode)) {
×
983
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Node does not belong to this document");
×
984
        }
985
        final IStoredNode<?> oldNode = (IStoredNode<?>) oldChild;
×
986
        final IStoredNode<?> newNode = (IStoredNode<?>) newChild;
×
987
        final IStoredNode<?> previousNode = (IStoredNode<?>) oldNode.getPreviousSibling();
×
988
        if(previousNode == null) {
×
989
            throw new DOMException(DOMException.NOT_FOUND_ERR, "No previous sibling for the old child");
×
990
        }
991
        try(final DBBroker broker = pool.getBroker()) {
×
992
            if(oldChild.getNodeType() == Node.ELEMENT_NODE) {
×
993
                // replace the document-element
994
                //TODO : be more precise in the type test -pb
995
                if(newChild.getNodeType() != Node.ELEMENT_NODE) {
×
996
                    throw new DOMException(
×
997
                        DOMException.INVALID_MODIFICATION_ERR,
×
998
                        "A node replacing the document root needs to be an element");
×
999
                }
1000
                broker.removeNode(transaction, oldNode, oldNode.getPath(), null);
×
1001
                broker.endRemove(transaction);
×
1002
                newNode.setNodeId(oldNode.getNodeId());
×
1003
                broker.insertNodeAfter(null, previousNode, newNode);
×
1004
                final NodePath path = newNode.getPath();
×
1005
                broker.indexNode(transaction, newNode, path);
×
1006
                broker.endElement(newNode, path, null);
×
1007
                broker.flush();
×
1008
            } else {
×
1009
                broker.removeNode(transaction, oldNode, oldNode.getPath(), null);
×
1010
                broker.endRemove(transaction);
×
1011
                newNode.setNodeId(oldNode.getNodeId());
×
1012
                broker.insertNodeAfter(transaction, previousNode, newNode);
×
1013
            }
1014
        } catch(final EXistException e) {
×
1015
            LOG.warn("Exception while updating child node: {}", e.getMessage(), e);
×
1016
            //TODO : thow exception ?
1017
        }
1018
        return newNode;
×
1019
    }
1020

1021
    @Override
1022
    @EnsureContainerLocked(mode=READ_LOCK)
1023
    public Node getFirstChild() {
1024
        if(children == 0) {
1!
1025
            return null;
×
1026
        }
1027
        try(final DBBroker broker = pool.getBroker()) {
1✔
1028
            return broker.objectWith(new NodeProxy(getExpression(), this, NodeId.DOCUMENT_NODE, childAddress[0]));
1✔
1029
        } catch(final EXistException e) {
×
1030
            LOG.warn("Exception while inserting node: {}", e.getMessage(), e);
×
1031
            //TODO : throw exception ?
1032
        }
1033
        return null;
×
1034
    }
1035

1036
    @EnsureContainerLocked(mode=READ_LOCK)
1037
    protected NodeProxy getFirstChildProxy() {
1038
        return new NodeProxy(getExpression(), this, NodeId.ROOT_NODE, Node.ELEMENT_NODE, childAddress[0]);
1✔
1039
    }
1040

1041
    /**
1042
     * The method <code>getFirstChildAddress</code>
1043
     *
1044
     * @return a <code>long</code> value
1045
     */
1046
    @EnsureContainerLocked(mode=READ_LOCK)
1047
    public long getFirstChildAddress() {
1048
        if(children == 0) {
1!
1049
            return StoredNode.UNKNOWN_NODE_IMPL_ADDRESS;
×
1050
        }
1051
        return childAddress[0];
1✔
1052
    }
1053

1054

1055
    @Override
1056
    public boolean hasChildNodes() {
1057
        return children > 0;
1!
1058
    }
1059

1060
    @Override
1061
    @EnsureContainerLocked(mode=READ_LOCK)
1062
    public NodeList getChildNodes() {
1063
        final org.exist.dom.NodeListImpl list = new org.exist.dom.NodeListImpl();
1✔
1064
        try(final DBBroker broker = pool.getBroker()) {
1✔
1065
            for(int i = 0; i < children; i++) {
1✔
1066
                final Node child = broker.objectWith(new NodeProxy(getExpression(), this, NodeId.DOCUMENT_NODE, childAddress[i]));
1✔
1067
                list.add(child);
1✔
1068
            }
1069
        } catch(final EXistException e) {
×
1070
            LOG.warn("Exception while retrieving child nodes: {}", e.getMessage(), e);
×
1071
        }
1072
        return list;
1✔
1073
    }
1074

1075
    /**
1076
     * The method <code>getPreviousSibling</code>
1077
     *
1078
     * @param node a <code>NodeHanle</code> value
1079
     * @return a <code>Node</code> value
1080
     */
1081
    protected Node getPreviousSibling(final NodeHandle node) {
1082
        final NodeList cl = getChildNodes();
×
1083
        for(int i = 0; i < cl.getLength(); i++) {
×
1084
            final NodeHandle next = (NodeHandle) cl.item(i);
×
1085
            if(StorageAddress.equals(node.getInternalAddress(), next.getInternalAddress())) {
×
1086
                return i == 0 ? null : cl.item(i - 1);
×
1087
            }
1088
        }
1089
        return null;
×
1090
    }
1091

1092
    /**
1093
     * The method <code>getFollowingSibling</code>
1094
     *
1095
     * @param node a <code>NodeHandle</code> value
1096
     * @return a <code>Node</code> value
1097
     */
1098
    @EnsureContainerLocked(mode=READ_LOCK)
1099
    protected Node getFollowingSibling(final NodeHandle node) {
1100
        final NodeList cl = getChildNodes();
×
1101
        for(int i = 0; i < cl.getLength(); i++) {
×
1102
            final NodeHandle next = (NodeHandle) cl.item(i);
×
1103
            if(StorageAddress.equals(node, next)) {
×
1104
                return i == children - 1 ? null : cl.item(i + 1);
×
1105
            }
1106
        }
1107
        return null;
×
1108
    }
1109

1110
    /**
1111
     * The method <code>findElementsByTagName</code>
1112
     *
1113
     * @param root  a <code>NodeHandle</code> value
1114
     * @param qname a <code>QName</code> value
1115
     * @return a <code>NodeList</code> value
1116
     */
1117
    protected NodeList findElementsByTagName(final NodeHandle root, final QName qname) {
1118
        try(final DBBroker broker = pool.getBroker()) {
1✔
1119

1120
            final MutableDocumentSet docs = new DefaultDocumentSet();
1✔
1121
            docs.add(this);
1✔
1122

1123
            final NewArrayNodeSet contextSet = new NewArrayNodeSet();
1✔
1124
            contextSet.add(new NodeProxy(getExpression(), this, root.getNodeId(), root.getInternalAddress()));
1✔
1125

1126
            return broker.getStructuralIndex().scanByType(ElementValue.ELEMENT, Constants.DESCENDANT_AXIS,
1✔
1127
                    new NameTest(Type.ELEMENT, qname), false, docs, contextSet, Expression.NO_CONTEXT_ID);
1✔
1128

1129
        } catch(final Exception e) {
×
1130
            LOG.warn("Exception while finding elements: {}", e.getMessage(), e);
×
1131
        }
1132
        return NodeSet.EMPTY_SET;
×
1133
    }
1134

1135
    /**
1136
     * The method <code>setXmlDeclaration</code>
1137
     *
1138
     * @param xmlDecl a <code>XMLDeclarationImpl</code> value
1139
     */
1140
    @EnsureContainerLocked(mode=WRITE_LOCK)
1141
    public void setXmlDeclaration(final XMLDeclarationImpl xmlDecl) {
1142
        this.xmlDecl = xmlDecl;
1✔
1143
    }
1✔
1144

1145
    @EnsureContainerLocked(mode=READ_LOCK)
1146
    public @Nullable XMLDeclarationImpl getXmlDeclaration() {
1147
        return xmlDecl;
1✔
1148
    }
1149

1150
    /************************************************
1151
     *
1152
     * NodeImpl methods
1153
     *
1154
     ************************************************/
1155

1156

1157
    /**
1158
     * The method <code>getDoctype</code>
1159
     *
1160
     * @return a <code>DocumentType</code> value
1161
     */
1162
    @Override
1163
    @EnsureContainerLocked(mode=READ_LOCK)
1164
    public DocumentType getDoctype() {
1165
        return docType;
1✔
1166
    }
1167

1168
    /**
1169
     * The method <code>setDocumentType</code>
1170
     *
1171
     * @param docType a <code>DocumentType</code> value
1172
     */
1173
    @EnsureContainerLocked(mode=WRITE_LOCK)
1174
    public void setDocumentType(final DocumentType docType) {
1175
        this.docType = docType;
1✔
1176
    }
1✔
1177

1178
    @Override
1179
    public DocumentImpl getOwnerDocument() {
1180
        return null;
1✔
1181
    }
1182

1183
    /**
1184
     * The method <code>setOwnerDocument</code>
1185
     *
1186
     * @param doc a <code>Document</code> value
1187
     */
1188
    public void setOwnerDocument(final Document doc) {
1189
        if(doc != this) {
×
1190
            throw new IllegalArgumentException("Can't set owner document");
×
1191
        }
1192
    }
×
1193

1194
    /**
1195
     * The method <code>getQName</code>
1196
     *
1197
     * @return a <code>QName</code> value
1198
     */
1199
    @Override
1200
    public QName getQName() {
1201
        return QName.DOCUMENT_QNAME;
×
1202
    }
1203

1204
    @Override
1205
    public void setQName(final QName qname) {
1206
        //do nothing
1207
    }
×
1208

1209
    /**
1210
     * The method <code>getNodeType</code>
1211
     *
1212
     * @return a <code>short</code> value
1213
     */
1214
    @Override
1215
    public short getNodeType() {
1216
        return Node.DOCUMENT_NODE;
1✔
1217
    }
1218

1219
    /**
1220
     * The method <code>getPreviousSibling</code>
1221
     *
1222
     * @return a <code>Node</code> value
1223
     */
1224
    @Override
1225
    public Node getPreviousSibling() {
1226
        //Documents don't have siblings
1227
        return null;
1✔
1228
    }
1229

1230
    /**
1231
     * The method <code>getNextSibling</code>
1232
     *
1233
     * @return a <code>Node</code> value
1234
     */
1235
    @Override
1236
    public Node getNextSibling() {
1237
        //Documents don't have siblings
1238
        return null;
1✔
1239
    }
1240

1241
    /**
1242
     * The method <code>createAttribute</code>
1243
     *
1244
     * @param name a <code>String</code> value
1245
     * @return an <code>Attr</code> value
1246
     * @throws DOMException if an error occurs
1247
     */
1248
    @Override
1249
    public Attr createAttribute(final String name) throws DOMException {
1250
        final QName qname;
1251
        try {
1252
            qname = new QName(name);
×
1253
        } catch (final IllegalQNameException e) {
×
1254
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1255
        }
1256

1257
        // check the QName is valid for use
1258
        if(qname.isValid(false) != QName.Validity.VALID.val) {
×
1259
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1260
        }
1261

1262
        final AttrImpl attr = new AttrImpl(getExpression(), qname, getBrokerPool().getSymbols());
×
1263
        attr.setOwnerDocument(this);
×
1264
        return attr;
×
1265
    }
1266

1267
    /**
1268
     * The method <code>createAttributeNS</code>
1269
     *
1270
     * @param namespaceURI  a <code>String</code> value
1271
     * @param qualifiedName a <code>String</code> value
1272
     * @return an <code>Attr</code> value
1273
     * @throws DOMException if an error occurs
1274
     */
1275
    @Override
1276
    public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) throws DOMException {
1277
        final QName qname;
1278

1279
        try {
1280
            qname = QName.parse(namespaceURI, qualifiedName);
×
1281
        } catch (final IllegalQNameException e) {
×
1282
            final short errCode;
1283
            if(e.getValidity() == ILLEGAL_FORMAT.val || (e.getValidity() & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1284
                errCode = DOMException.NAMESPACE_ERR;
×
1285
            } else {
×
1286
                errCode = DOMException.INVALID_CHARACTER_ERR;
×
1287
            }
1288
            throw new DOMException(errCode, "qualified name is invalid");
×
1289
        }
1290

1291
        // check the QName is valid for use
1292
        final byte validity = qname.isValid(false);
×
1293
        if((validity & QName.Validity.INVALID_LOCAL_PART.val) == QName.Validity.INVALID_LOCAL_PART.val) {
×
1294
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "qualified name is invalid");
×
1295
        } else if((validity & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1296
            throw new DOMException(DOMException.NAMESPACE_ERR, "qualified name is invalid");
×
1297
        }
1298

1299
        final AttrImpl attr = new AttrImpl(getExpression(), qname, getBrokerPool().getSymbols());
×
1300
        attr.setOwnerDocument(this);
×
1301
        return attr;
×
1302
    }
1303

1304
    /**
1305
     * The method <code>createElement</code>
1306
     *
1307
     * @param tagName a <code>String</code> value
1308
     * @return an <code>Element</code> value
1309
     * @throws DOMException if an error occurs
1310
     */
1311
    @Override
1312
    public Element createElement(final String tagName) throws DOMException {
1313
        final QName qname;
1314

1315
        try {
1316
            qname = new QName(tagName);
×
1317
        } catch (final IllegalQNameException e) {
×
1318
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1319
        }
1320

1321
        // check the QName is valid for use
1322
        if(qname.isValid(false) != QName.Validity.VALID.val) {
×
1323
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1324
        }
1325

1326
        final ElementImpl element = new ElementImpl(getExpression(), qname, getBrokerPool().getSymbols());
×
1327
        element.setOwnerDocument(this);
×
1328
        return element;
×
1329
    }
1330

1331
    /**
1332
     * The method <code>createElementNS</code>
1333
     *
1334
     * @param namespaceURI  a <code>String</code> value
1335
     * @param qualifiedName a <code>String</code> value
1336
     * @return an <code>Element</code> value
1337
     * @throws DOMException if an error occurs
1338
     */
1339
    @Override
1340
    public Element createElementNS(final String namespaceURI, final String qualifiedName) throws DOMException {
1341
        final QName qname;
1342
        try {
1343
            qname = QName.parse(namespaceURI, qualifiedName);
×
1344
        } catch (final IllegalQNameException e) {
×
1345
            final short errCode;
1346
            if(e.getValidity() == ILLEGAL_FORMAT.val || (e.getValidity() & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1347
                errCode = DOMException.NAMESPACE_ERR;
×
1348
            } else {
×
1349
                errCode = DOMException.INVALID_CHARACTER_ERR;
×
1350
            }
1351
            throw new DOMException(errCode, "qualified name is invalid");
×
1352
        }
1353

1354
        // check the QName is valid for use
1355
        final byte validity = qname.isValid(false);
×
1356
        if((validity & QName.Validity.INVALID_LOCAL_PART.val) == QName.Validity.INVALID_LOCAL_PART.val) {
×
1357
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "qualified name is invalid");
×
1358
        } else if((validity & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1359
            throw new DOMException(DOMException.NAMESPACE_ERR, "qualified name is invalid");
×
1360
        }
1361

1362
        final ElementImpl element = new ElementImpl(getExpression(), qname, getBrokerPool().getSymbols());
×
1363
        element.setOwnerDocument(this);
×
1364
        return element;
×
1365
    }
1366

1367
    /**
1368
     * The method <code>createTextNode</code>
1369
     *
1370
     * @param data a <code>String</code> value
1371
     * @return a <code>Text</code> value
1372
     */
1373
    @Override
1374
    public Text createTextNode(final String data) {
1375
        final TextImpl text = new TextImpl(getExpression(), data);
×
1376
        text.setOwnerDocument(this);
×
1377
        return text;
×
1378
    }
1379

1380
    /*
1381
     *  W3C Document-Methods
1382
     */
1383

1384
    /**
1385
     * The method <code>getDocumentElement</code>
1386
     *
1387
     * @return an <code>Element</code> value
1388
     */
1389
    @Override
1390
    public Element getDocumentElement() {
1391
        final NodeList cl = getChildNodes();
1✔
1392
        for(int i = 0; i < cl.getLength(); i++) {
1!
1393
            if(cl.item(i).getNodeType() == Node.ELEMENT_NODE) {
1✔
1394
                return (Element) cl.item(i);
1✔
1395
            }
1396
        }
1397
        return null;
×
1398
    }
1399

1400
    @Override
1401
    public NodeList getElementsByTagName(final String tagname) {
1402
        if(tagname != null && tagname.equals(QName.WILDCARD)) {
1!
1403
            return getElementsByTagName(new QName.WildcardLocalPartQName(XMLConstants.DEFAULT_NS_PREFIX));
1✔
1404
        } else {
1405
            try {
1406
                return getElementsByTagName(new QName(tagname));
×
1407
            } catch (final IllegalQNameException e) {
×
1408
                throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1409
            }
1410
        }
1411
    }
1412

1413
    @Override
1414
    public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
1415
        final boolean wildcardNS = namespaceURI != null && namespaceURI.equals(QName.WILDCARD);
1!
1416
        final boolean wildcardLocalPart = localName != null && localName.equals(QName.WILDCARD);
1!
1417

1418
        if(wildcardNS && wildcardLocalPart) {
1!
1419
            return getElementsByTagName(QName.WildcardQName.getInstance());
1✔
1420
        } else if(wildcardNS) {
1!
1421
            return getElementsByTagName(new QName.WildcardNamespaceURIQName(localName));
1✔
1422
        } else if(wildcardLocalPart) {
×
1423
            return getElementsByTagName(new QName.WildcardLocalPartQName(namespaceURI));
×
1424
        } else {
1425
            return getElementsByTagName(new QName(localName, namespaceURI));
×
1426
        }
1427
    }
1428

1429
    private NodeList getElementsByTagName(final QName qname) {
1430
        try(final DBBroker broker = pool.getBroker()) {
1✔
1431

1432
            final MutableDocumentSet docs = new DefaultDocumentSet();
1✔
1433
            docs.add(this);
1✔
1434

1435
            final NewArrayNodeSet contextSet = new NewArrayNodeSet();
1✔
1436
            final ElementImpl root = ((ElementImpl)getDocumentElement());
1✔
1437
            contextSet.add(new NodeProxy(getExpression(), this, root.getNodeId(), root.getInternalAddress()));
1✔
1438

1439
            return broker.getStructuralIndex().scanByType(ElementValue.ELEMENT, Constants.DESCENDANT_SELF_AXIS,
1✔
1440
                    new NameTest(Type.ELEMENT, qname), false, docs, contextSet, Expression.NO_CONTEXT_ID);
1✔
1441
        } catch(final Exception e) {
×
1442
            LOG.error("Exception while finding elements: {}", e.getMessage(), e);
×
1443
            //TODO : throw exception ?
1444
        }
1445
        return NodeSet.EMPTY_SET;
×
1446
    }
1447

1448
    @Override
1449
    public Node getParentNode() {
1450
        //Documents don't have parents
1451
        return null;
1✔
1452
    }
1453

1454
    /**
1455
     * The method <code>getChildCount</code>
1456
     *
1457
     * @return an <code>int</code> value
1458
     */
1459
    @Override
1460
    @EnsureContainerLocked(mode=READ_LOCK)
1461
    public int getChildCount() {
1462
        return children;
1✔
1463
    }
1464

1465
    @EnsureContainerLocked(mode=WRITE_LOCK)
1466
    public void setChildCount(final int count) {
1467
        this.children = count;
1✔
1468
        if(children == 0) {
1!
1469
            this.childAddress = null;
1✔
1470
        }
1471
    }
1✔
1472

1473
    @Override
1474
    @EnsureContainerLocked(mode=READ_LOCK)
1475
    public boolean isSameNode(final Node other) {
1476
        // This function is used by Saxon in some circumstances, and this partial implementation is required for proper Saxon operation.
1477
        if(other instanceof DocumentImpl) {
1✔
1478
            return this.docId == ((DocumentImpl) other).getDocId();
1✔
1479
        } else {
1480
            return false;
1✔
1481
        }
1482
    }
1483

1484
    @Override
1485
    public CDATASection createCDATASection(final String data) throws DOMException {
1486
        final CDATASectionImpl cdataSection = new CDATASectionImpl(getExpression(), new XMLString(data.toCharArray()));
×
1487
        cdataSection.setOwnerDocument(this);
×
1488
        return cdataSection;
×
1489
    }
1490

1491
    @Override
1492
    public Comment createComment(final String data) {
1493
        final CommentImpl comment = new CommentImpl(getExpression(), data);
×
1494
        comment.setOwnerDocument(this);
×
1495
        return comment;
×
1496
    }
1497

1498
    @Override
1499
    public ProcessingInstruction createProcessingInstruction(final String target, final String data)
1500
            throws DOMException {
1501
        final ProcessingInstructionImpl processingInstruction = new ProcessingInstructionImpl((Expression) null, target, data);
×
1502
        processingInstruction.setOwnerDocument(this);
×
1503
        return processingInstruction;
×
1504
    }
1505

1506
    @Override
1507
    public DocumentFragment createDocumentFragment() throws DOMException {
1508
        return new DocumentFragmentImpl(getExpression());
×
1509
    }
1510

1511
    @Override
1512
    public EntityReference createEntityReference(final String name) throws DOMException {
1513
        throw unsupported();
×
1514
    }
1515

1516
    @Override
1517
    public Element getElementById(final String elementId) {
1518
        throw unsupported();
×
1519
    }
1520

1521
    @Override
1522
    public org.w3c.dom.DOMImplementation getImplementation() {
1523
        return new DOMImplementationImpl();
×
1524
    }
1525

1526
    @Override
1527
    public boolean getStrictErrorChecking() {
1528
        throw unsupported();
×
1529
    }
1530

1531
    @Override
1532
    public Node adoptNode(final Node node) throws DOMException {
1533
        throw unsupported();
×
1534
    }
1535

1536
    @Override
1537
    public Node importNode(final Node importedNode, final boolean deep) throws DOMException {
1538
        throw unsupported();
×
1539
    }
1540

1541
    @Override
1542
    public void setStrictErrorChecking(final boolean strict) {
1543
        throw unsupported();
×
1544
    }
1545

1546
    @Override
1547
    public String getInputEncoding() {
1548
        throw unsupported();
×
1549
    }
1550

1551
    @Override
1552
    public String getXmlEncoding() {
1553
        return UTF_8.name();    //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1554
    }
1555

1556
    @Override
1557
    public boolean getXmlStandalone() {
1558
        return false;   //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1559
    }
1560

1561
    @Override
1562
    public void setXmlStandalone(final boolean xmlStandalone) throws DOMException {
1563
    }
×
1564

1565
    @Override
1566
    public String getXmlVersion() {
1567
        return "1.0";   //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1568
    }
1569

1570
    @Override
1571
    public void setXmlVersion(final String xmlVersion) throws DOMException {
1572
    }
×
1573

1574
    @Override
1575
    public String getDocumentURI() {
1576
        return getBaseURI();
1✔
1577
    }
1578

1579
    @Override
1580
    public void setDocumentURI(final String documentURI) {
1581
        throw unsupported();
×
1582
    }
1583

1584
    @Override
1585
    public DOMConfiguration getDomConfig() {
1586
        throw unsupported();
×
1587
    }
1588

1589
    @Override
1590
    public void normalizeDocument() {
1591
        throw unsupported();
×
1592
    }
1593

1594
    @Override
1595
    public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName) throws DOMException {
1596
        throw unsupported();
×
1597
    }
1598

1599
    @Override
1600
    public String getBaseURI() {
1601
        return getURI().toString();
1✔
1602
    }
1603

1604
    @Override
1605
    public String toString() {
1606
        return getURI() + " - <" +
×
1607
            (getDocumentElement() != null ? getDocumentElement().getNodeName() : null) + ">";
×
1608
    }
1609

1610
    @Override
1611
    public NodeId getNodeId() {
1612
        return null;
1✔
1613
    }
1614

1615
    @Override
1616
    public Node appendChild(final Node newChild) throws DOMException {
1617
        if(newChild.getNodeType() != Node.DOCUMENT_NODE && newChild.getOwnerDocument() != this) {
×
1618
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Owning document IDs do not match");
×
1619
        }
1620

1621
        if(newChild == this) {
×
1622
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1623
                    "Cannot append a document to itself");
×
1624
        }
1625

1626
        if(newChild.getNodeType() == DOCUMENT_NODE) {
×
1627
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1628
                    "A Document Node may not be appended to a Document Node");
×
1629
        }
1630

1631
        if(newChild.getNodeType() == ELEMENT_NODE && getDocumentElement() != null) {
×
1632
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1633
                    "A Document Node may only have a single document element");
×
1634
        }
1635

1636
        if(newChild.getNodeType() == DOCUMENT_TYPE_NODE && getDoctype() != null) {
×
1637
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1638
                    "A Document Node may only have a single document type");
×
1639
        }
1640

1641
        throw unsupported();
×
1642
    }
1643
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc