• 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

35.9
/extensions/webdav/src/main/java/org/exist/webdav/MiltonCollection.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.webdav;
50

51
import com.bradmcevoy.http.*;
52
import com.bradmcevoy.http.exceptions.*;
53
import org.exist.EXistException;
54
import org.exist.security.PermissionDeniedException;
55
import org.exist.security.Subject;
56
import org.exist.storage.BrokerPool;
57
import org.exist.util.UUIDGenerator;
58
import org.exist.webdav.ExistResource.Mode;
59
import org.exist.webdav.exceptions.CollectionDoesNotExistException;
60
import org.exist.webdav.exceptions.CollectionExistsException;
61
import org.exist.xmldb.XmldbURI;
62

63
import javax.xml.stream.XMLOutputFactory;
64
import javax.xml.stream.XMLStreamException;
65
import javax.xml.stream.XMLStreamWriter;
66
import java.io.IOException;
67
import java.io.InputStream;
68
import java.io.OutputStream;
69
import java.util.*;
70

71
/**
72
 * Class for representing a collection as a Milton WebDAV collection.
73
 * See <a href="http://milton.ettrema.com">Milton</a>.
74
 *
75
 * @author Dannes Wessels (dizzzz_at_exist-db.org)
76
 */
77
public class MiltonCollection extends MiltonResource
78
        implements CollectionResource, GetableResource, PropFindableResource,
79
        DeletableResource, MakeCollectionableResource, PutableResource, LockingCollectionResource 
80
        /*, DigestResource */, MoveableResource, CopyableResource, LockNullResource {
81

82
    private final ExistCollection existCollection;
83

84
    /**
85
     * Constructor of representation of a Collection in the Milton framework, without subject information.
86
     * To be called by the resource factory.
87
     *
88
     * @param configuration any configuration properties.
89
     * @param host FQ host name including port number.
90
     * @param uri  Path on server indicating path of resource
91
     * @param pool Handle to Exist database.
92
     */
93
    public MiltonCollection(final Properties configuration, String host, XmldbURI uri, BrokerPool pool) {
94
        this(configuration, host, uri, pool, null);
1✔
95
    }
1✔
96

97
    /**
98
     * Constructor of representation of a Document in the Milton framework, with subject information.
99
     * To be called by the resource factory.
100
     *
101
     * @param configuration any configuration properties.
102
     * @param host    FQ host name including port number.
103
     * @param uri     Path on server indicating path of resource.
104
     * @param subject An Exist operation is performed with Subject. Can be NULL.
105
     * @param pool    Handle to Exist database.
106
     */
107
    public MiltonCollection(final Properties configuration, String host, XmldbURI uri, BrokerPool pool, Subject subject) {
108
        super(configuration);
1✔
109

110
        if (LOG.isDebugEnabled()) {
1!
111
            LOG.debug("COLLECTION={}", uri.toString());
×
112
        }
113

114
        resourceXmldbUri = uri;
1✔
115
        brokerPool = pool;
1✔
116
        this.host = host;
1✔
117

118
        existCollection = new ExistCollection(configuration, uri, brokerPool);
1✔
119

120
        // store simpler type
121
        existResource = existCollection;
1✔
122

123
        // If subject is available, additional data can be retrieved.
124
        if (subject != null) {
1✔
125
            existCollection.setUser(subject);
1✔
126
            existCollection.initMetadata();
1✔
127
        }
128
    }
1✔
129

130
    /* ===================
131
     * Collection Resource
132
     * =================== */
133
    @Override
134
    public Resource child(String childName) {
135

136
        if (LOG.isDebugEnabled()) {
1!
137
            LOG.debug("get child={}", childName);
×
138
        }
139

140
        // Safe guard value
141
        if (childName == null) {
1!
142
            return null;
×
143
        }
144

145
        // Search all resources, return resource upon match
146
        List<? extends Resource> allResources = getChildren();
1✔
147
        for (Resource resource : allResources) {
1✔
148
            if (childName.equals(resource.getName())) {
1!
149
                return resource;
×
150
            }
151
        }
1✔
152

153
        // Not found
154
        return null;
1✔
155
    }
156

157
    private List<MiltonCollection> getCollectionResources() {
158
        List<MiltonCollection> allResources = new ArrayList<>();
1✔
159
        for (XmldbURI path : existCollection.getCollectionURIs()) {
1✔
160
            allResources.add(new MiltonCollection(configuration, this.host, path, brokerPool, subject));
1✔
161
        }
1✔
162
        return allResources;
1✔
163
    }
164

165
    private List<MiltonDocument> getDocumentResources() {
166
        List<MiltonDocument> allResources = new ArrayList<>();
1✔
167
        for (XmldbURI path : existCollection.getDocumentURIs()) {
1✔
168
            MiltonDocument mdoc = new MiltonDocument(configuration, this.host, path, brokerPool, subject);
1✔
169
            // Show (restimated) size for PROPFIND only
170
            mdoc.setIsPropFind(true);
1✔
171
            allResources.add(mdoc);
1✔
172
        }
1✔
173
        return allResources;
1✔
174
    }
175

176
    @Override
177
    public List<? extends Resource> getChildren() {
178
        List<Resource> allResources = new ArrayList<>();
1✔
179

180
        allResources.addAll(getCollectionResources());
1✔
181
        allResources.addAll(getDocumentResources());
1✔
182

183
        if (LOG.isDebugEnabled()) {
1!
184
            LOG.debug("Nr of children={}", allResources.size());
×
185
        }
186

187
        return allResources;
1✔
188
    }
189

190

191
    /* ====================
192
     * PropFindableResource
193
     * ==================== */
194
    @Override
195
    public Date getCreateDate() {
196

197
        Date createDate = null;
1✔
198

199
        Long time = existCollection.getCreationTime();
1✔
200
        if (time != null) {
1!
201
            createDate = new Date(time);
1✔
202
        }
203

204
        if (LOG.isTraceEnabled()) {
1!
205
            LOG.trace("Create date={}", createDate);
×
206
        }
207

208
        return createDate;
1✔
209
    }
210

211
    /* ====================
212
     * DeletableResource
213
     * ==================== */
214
    @Override
215
    public void delete() throws NotAuthorizedException, ConflictException, BadRequestException {
216

217
        if (LOG.isDebugEnabled()) {
×
218
            LOG.debug("Delete collection '{}'.", resourceXmldbUri);
×
219
        }
220

221
        existCollection.delete();
×
222
    }
×
223

224
    /* ==========================
225
     * MakeCollectionableResource
226
     * ========================== */
227
    @Override
228
    public CollectionResource createCollection(String name)
229
            throws NotAuthorizedException, ConflictException {
230

231
        if (LOG.isTraceEnabled()) {
×
232
            LOG.trace("Create collection '{}' in '{}'.", name, resourceXmldbUri);
×
233
        }
234

235
        CollectionResource collection = null;
×
236
        try {
237
            XmldbURI collectionURI = existCollection.createCollection(name);
×
238
            collection = new MiltonCollection(configuration, host, collectionURI, brokerPool, subject);
×
239

240
        } catch (PermissionDeniedException ex) {
×
241
            LOG.debug(ex.getMessage());
×
242
            throw new NotAuthorizedException(this);
×
243

244
        } catch (CollectionExistsException | EXistException ex) {
×
245
            LOG.debug(ex.getMessage());
×
246
            throw new ConflictException(this, "Create Collection '" + getXmldbUri().append(name) + "' failed: " + ex.getMessage());
×
247

248
        }
×
249

250
        return collection;
×
251
    }
252

253

254
    /* ===============
255
     * PutableResource
256
     * =============== */
257
    @Override
258
    public Resource createNew(String newName, InputStream is, Long length, String contentType)
259
            throws IOException, ConflictException {
260

261
        if (LOG.isTraceEnabled()) {
1!
262
            LOG.trace("Create '{}' in '{}'", newName, resourceXmldbUri);
×
263
        }
264

265
        Resource resource = null;
1✔
266
        try {
267
            // submit
268
            XmldbURI resourceURI = existCollection.createFile(newName, is, length, contentType);
1✔
269

270
            resource = new MiltonDocument(configuration, host, resourceURI, brokerPool, subject);
1✔
271

272
        } catch (PermissionDeniedException | CollectionDoesNotExistException | IOException e) {
×
273
            LOG.debug(e.getMessage());
×
274
            throw new ConflictException(this, "Create New '" + getXmldbUri().append(newName) + "' failed: " + e.getMessage());
×
275
        }
1✔
276
        return resource;
1✔
277
    }
278

279
    /* =========================
280
     * LockingCollectionResource
281
     * ========================= */
282
    @Override
283
    public LockToken createAndLock(String name, LockTimeout timeout, LockInfo lockInfo) throws NotAuthorizedException {
284

285
        if (LOG.isDebugEnabled()) {
×
286
            LOG.debug("'{}' name='{}'", resourceXmldbUri, name);
×
287
        }
288

289
        String token = UUIDGenerator.getUUIDversion4();
×
290

291
        return new LockToken(token, lockInfo, timeout);
×
292
    }
293

294

295
    /* ================
296
     * LockableResource
297
     * ================ */
298
    @Override
299
    public LockResult lock(LockTimeout timeout, LockInfo lockInfo)
300
            throws NotAuthorizedException, PreConditionFailedException, LockedException {
301

302
        if (LOG.isDebugEnabled()) {
×
303
            LOG.debug("'{}' -- {}", resourceXmldbUri, lockInfo.toString());
×
304
        }
305

306
        return refreshLock(UUIDGenerator.getUUIDversion4());
×
307
    }
308

309
    @Override
310
    public LockResult refreshLock(String token) throws NotAuthorizedException, PreConditionFailedException {
311

312
        if (LOG.isDebugEnabled()) {
×
313
            LOG.debug("'{}' token='{}'", resourceXmldbUri, token);
×
314
        }
315

316
        LockInfo lockInfo = new LockInfo(LockInfo.LockScope.NONE, LockInfo.LockType.READ, token, LockInfo.LockDepth.ZERO);
×
317
        LockTimeout lockTime = new LockTimeout(Long.MAX_VALUE);
×
318

319
        LockToken lockToken = new LockToken(token, lockInfo, lockTime);
×
320

321
        return new LockResult(LockResult.FailureReason.PRECONDITION_FAILED, lockToken);
×
322
    }
323

324
    @Override
325
    public void unlock(String tokenId) throws NotAuthorizedException, PreConditionFailedException {
326
        // Just do nothing
327
        if (LOG.isDebugEnabled()) {
×
328
            LOG.debug("'{}' token='{}'", resourceXmldbUri, tokenId);
×
329
        }
330
    }
×
331

332
    @Override
333
    public LockToken getCurrentLock() {
334
        if (LOG.isDebugEnabled()) {
1!
335
            LOG.debug("'{}'", resourceXmldbUri);
×
336
        }
337
        return null; // null is allowed
1✔
338
    }
339

340

341
    /* ===============
342
     * MovableResource
343
     * =============== */
344
    @Override
345
    public void moveTo(CollectionResource rDest, String newName) throws ConflictException {
346

347
        if (LOG.isDebugEnabled()) {
×
348
            LOG.debug("Move '{}' to '{}' in '{}'", resourceXmldbUri, newName, rDest.getName());
×
349
        }
350

351
        XmldbURI destCollection = ((MiltonCollection) rDest).getXmldbUri();
×
352
        try {
353
            existCollection.resourceCopyMove(destCollection, newName, Mode.MOVE);
×
354

355
        } catch (EXistException ex) {
×
356
            throw new ConflictException(this, "Move '" + getXmldbUri() + "' to '" + destCollection.append(newName) + "' failed: " + ex.getMessage());
×
357
        }
×
358
    }
×
359

360
    /* ================
361
     * CopyableResource
362
     * ================ */
363

364
    @Override
365
    public void copyTo(CollectionResource toCollection, String newName) {
366

367
        if (LOG.isDebugEnabled()) {
×
368
            LOG.debug("Move '{}' to '{}' in '{}'", resourceXmldbUri, newName, toCollection.getName());
×
369
        }
370

371
        XmldbURI destCollection = ((MiltonCollection) toCollection).getXmldbUri();
×
372
        try {
373
            existCollection.resourceCopyMove(destCollection, newName, Mode.COPY);
×
374

375
        } catch (EXistException ex) {
×
376
            // copyTo does not throw COnflictException
377
            LOG.error(ex.getMessage());
×
378
        }
×
379
    }
×
380

381
    /* ================
382
     * GettableResource
383
     * ================ */
384

385
    @Override
386
    public void sendContent(OutputStream out, Range range, Map<String, String> params,
387
                            String contentType) throws IOException, NotAuthorizedException, BadRequestException {
388

389
        try {
390
            XMLOutputFactory xf = XMLOutputFactory.newInstance();
×
391
            xf.setProperty("javax.xml.stream.isRepairingNamespaces", Boolean.TRUE);
×
392

393
            XMLStreamWriter writer = xf.createXMLStreamWriter(out);
×
394
            writer.setDefaultNamespace("http://exist.sourceforge.net/NS/exist");
×
395

396
            // Begin document
397
            writer.writeStartDocument();
×
398

399
            writer.writeComment("Warning: this XML format is *not* part of the WebDAV specification.");
×
400

401
            // Root element
402
            writer.writeStartElement("exist", "result", "http://exist.sourceforge.net/NS/exist");
×
403

404
            // Root collection
405
            writer.writeStartElement("exist", "collection", "http://exist.sourceforge.net/NS/exist");
×
406
            writer.writeAttribute("name", resourceXmldbUri.lastSegment().toString());
×
407
            writer.writeAttribute("created", getXmlDateTime(existCollection.getCreationTime()));
×
408
            writer.writeAttribute("owner", existCollection.getOwnerUser());
×
409
            writer.writeAttribute("group", existCollection.getOwnerGroup());
×
410
            writer.writeAttribute("permissions", "" + existCollection.getPermissions().toString());
×
411

412

413
            // Iterate over all collections in collection
414
            for (MiltonCollection collection : getCollectionResources()) {
×
415
                collection.writeXML(writer);
×
416
            }
×
417

418
            // Iterate over all documents in collection
419
            for (MiltonDocument document : getDocumentResources()) {
×
420
                document.writeXML(writer);
×
421
            }
×
422

423
            // Finish top collection
424
            writer.writeEndElement();
×
425

426
            // Finish root element
427
            writer.writeEndElement();
×
428

429
            // Finish document
430
            writer.writeEndDocument();
×
431

432
        } catch (XMLStreamException ex) {
×
433
            LOG.error(ex);
×
434
            throw new IOException(ex.getMessage());
×
435
        }
×
436
    }
×
437

438
    @Override
439
    public Long getMaxAgeSeconds(Auth auth) {
440
        return null;
×
441
    }
442

443
    @Override
444
    public String getContentType(String accepts) {
445
        return "application/xml";
1✔
446
    }
447

448
    @Override
449
    public Long getContentLength() {
450
        return null;
1✔
451
    }
452

453
    /* ================
454
     * StAX serializer
455
     * ================ */
456

457
    private void writeXML(XMLStreamWriter writer) throws XMLStreamException {
458
        writer.writeStartElement("exist", "collection", "http://exist.sourceforge.net/NS/exist");
×
459
        writer.writeAttribute("name", resourceXmldbUri.lastSegment().toString());
×
460
        writer.writeAttribute("created", getXmlDateTime(existCollection.getCreationTime()));
×
461
        writer.writeAttribute("owner", existCollection.getOwnerUser());
×
462
        writer.writeAttribute("group", existCollection.getOwnerGroup());
×
463
        writer.writeAttribute("permissions", existCollection.getPermissions().toString());
×
464
        writer.writeEndElement();
×
465
    }
×
466

467
}
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