• 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

91.49
/exist-core/src/main/java/org/exist/source/SourceFactory.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.source;
50

51
import java.io.IOException;
52
import java.net.MalformedURLException;
53
import java.net.URI;
54
import java.net.URISyntaxException;
55
import java.net.URL;
56
import java.nio.file.Files;
57
import java.nio.file.InvalidPathException;
58
import java.nio.file.Path;
59
import java.nio.file.Paths;
60

61
import org.apache.logging.log4j.LogManager;
62
import org.apache.logging.log4j.Logger;
63
import org.exist.EXistException;
64
import org.exist.dom.persistent.BinaryDocument;
65
import org.exist.dom.persistent.DocumentImpl;
66
import org.exist.dom.persistent.LockedDocument;
67
import org.exist.security.PermissionDeniedException;
68
import org.exist.storage.BrokerPool;
69
import org.exist.storage.DBBroker;
70
import org.exist.storage.lock.Lock.LockMode;
71
import org.exist.storage.serializers.Serializer;
72
import org.exist.util.FileUtils;
73
import org.exist.xmldb.XmldbURI;
74
import org.xml.sax.SAXException;
75

76
import javax.annotation.Nullable;
77

78
/**
79
 * Factory to create a {@link org.exist.source.Source} object for a given
80
 * URL.
81
 *
82
 * @author wolf
83
 */
84
public class SourceFactory {
×
85

86
    private final static Logger LOG = LogManager.getLogger(SourceFactory.class);
1✔
87

88
    /**
89
     * Create a {@link Source} object for the given resource URL.
90
     *
91
     * As a special case, if the URL starts with "resource:", the resource
92
     * will be read from the current context class loader.
93
     *
94
     * @param broker the DBBroker
95
     * @param contextPath the context path of the resource.
96
     * @param location the location of the resource (relative to the {@code contextPath}).
97
     * @param checkXQEncoding where we need to check the encoding of the XQuery.
98
     *
99
     * @return The Source of the resource, or null if the resource cannot be found.
100
     *
101
     * @throws PermissionDeniedException if the resource resides in the database,
102
     *     but the calling user does not have permission to access it.
103
     * @throws IOException if a general I/O error occurs whilst accessing the resource.
104
     */
105
    public static @Nullable Source getSource(final DBBroker broker, @Nullable final String contextPath, final String location, final boolean checkXQEncoding) throws IOException, PermissionDeniedException {
106
        Source source = null;
1✔
107

108
        /* resource: */
109
        if (location.startsWith(ClassLoaderSource.PROTOCOL)
1✔
110
                || (contextPath != null && contextPath.startsWith(ClassLoaderSource.PROTOCOL))) {
1✔
111
            source = getSource_fromClasspath(contextPath, location);
1✔
112
        }
113

114
        /* xmldb */
115
        if (source == null
1✔
116
                && (location.startsWith(XmldbURI.XMLDB_URI_PREFIX)
1✔
117
                || (contextPath != null && contextPath.startsWith(XmldbURI.XMLDB_URI_PREFIX)))) {
1✔
118

119
            XmldbURI pathUri;
120
            try {
121
                if (contextPath == null) {
1✔
122
                    pathUri = XmldbURI.create(location);
1✔
123
                } else {
1✔
124
                    pathUri = XmldbURI.create(contextPath).append(location);
1✔
125
                }
126
            } catch (final IllegalArgumentException e) {
1✔
127
                // this is allowed if the location is already an absolute URI, below we will try using other schemes
128
                pathUri = null;
1✔
129
            }
130

131
            if (pathUri != null) {
1✔
132
                source = getSource_fromDb(broker, pathUri);
1✔
133
            }
134
        }
135

136
        /* /db */
137
        if (source == null
1✔
138
                && ((location.startsWith("/db") && !Files.exists(Paths.get(firstPathSegment(location))))
1!
139
                || (contextPath != null && contextPath.startsWith("/db") && !Files.exists(Paths.get(firstPathSegment(contextPath)))))) {
1!
140
            final XmldbURI pathUri;
141
            if (contextPath == null || (".".equals(contextPath) && location.startsWith("/"))) {
1!
142
                pathUri = XmldbURI.create(location);
1✔
143
            } else {
1✔
144
                pathUri = XmldbURI.create(contextPath).append(location);
1✔
145
            }
146
            source = getSource_fromDb(broker, pathUri);
1✔
147
        }
148

149
        /* file:// or location without scheme (:/) is assumed to be a file */
150
        if (source == null
1✔
151
                && (location.startsWith("file:/")
1✔
152
                || !location.contains(":/"))) {
1✔
153
            source = getSource_fromFile(contextPath, location, checkXQEncoding);
1✔
154
        }
155

156
        /* final attempt - any other URL */
157
        if (source == null
1✔
158
                && !(
159
                        location.startsWith(ClassLoaderSource.PROTOCOL)
1!
160
                        || location.startsWith(XmldbURI.XMLDB_URI_PREFIX)
1✔
161
                        || location.startsWith("file:/"))
1!
162
                ) {
163
            try {
164
                final URL url = new URL(location);
1✔
165
                source = new URLSource(url);
1✔
166
            } catch (final MalformedURLException e) {
1✔
167
                return null;
1✔
168
            }
169
        }
170

171
        return source;
1✔
172
    }
173

174
    private static String firstPathSegment(final String path) {
175
        return XmldbURI
1✔
176
                .create(path)
1✔
177
                .getPathSegments()[0]
1✔
178
                .getRawCollectionPath();
1✔
179
    }
180

181
    private static Source getSource_fromClasspath(final String contextPath, final String location) throws IOException {
182
        if (contextPath == null || location.startsWith(ClassLoaderSource.PROTOCOL)) {
1!
183
            return new ClassLoaderSource(location);
1✔
184
        }
185

186
        final Path rootPath = Paths.get(contextPath.substring(ClassLoaderSource.PROTOCOL.length()));
1✔
187

188
        // 1) try resolving location as child
189
        final Path childLocation = rootPath.resolve(location);
1✔
190
        try {
191
            return new ClassLoaderSource(ClassLoaderSource.PROTOCOL + childLocation.toString().replace('\\', '/'));
1✔
192
        } catch (final IOException e) {
1✔
193
            // no-op, we will try again below
194
        }
195

196
        // 2) try resolving location as sibling
197
        final Path siblingLocation = rootPath.resolveSibling(location);
1✔
198
        return new ClassLoaderSource(ClassLoaderSource.PROTOCOL + siblingLocation.toString().replace('\\', '/'));
1✔
199
    }
200

201
    /**
202
     * Get the resource source from the database.
203
     *
204
     * @param broker The database broker.
205
     * @param path The path to the resource in the database.
206
     *
207
     * @return the source, or null if there is no such resource in the db indicated by {@code path}.
208
     */
209
    private static @Nullable Source getSource_fromDb(final DBBroker broker, final XmldbURI path) throws PermissionDeniedException, IOException {
210
        Source source = null;
1✔
211
        try(final LockedDocument lockedResource = broker.getXMLResource(path, LockMode.READ_LOCK)) {
1✔
212
            if (lockedResource != null) {
1✔
213
                final DocumentImpl resource = lockedResource.getDocument();
1✔
214
                if (resource.getResourceType() == DocumentImpl.BINARY_FILE) {
1✔
215
                    source = new DBSource(broker.getBrokerPool(), (BinaryDocument) resource, true);
1✔
216
                } else {
1✔
217
                    final Serializer serializer = broker.borrowSerializer();
1✔
218
                    try {
219
                        // XML document: serialize to string source so it can be read as a stream
220
                        // by fn:unparsed-text and friends
221
                        source = new StringSource(serializer.serialize(resource));
1✔
222
                    } catch (final SAXException e) {
1✔
223
                        throw new IOException(e.getMessage());
×
224
                    } finally {
225
                        broker.returnSerializer(serializer);
1✔
226
                    }
227
                }
228
            }
229
        }
230
        return source;
1✔
231
    }
232

233
    /**
234
     * Get the resource source from the filesystem.
235
     *
236
     * @param contextPath the context path of the resource.
237
     * @param location the location of the resource (relative to the {@code contextPath}).
238
     * @param checkXQEncoding where we need to check the encoding of the XQuery.
239
     *
240
     * @return the source, or null if there is no such resource in the db indicated by {@code path}.
241
     */
242
    private static @Nullable Source getSource_fromFile(@Nullable final String contextPath, final String location, final boolean checkXQEncoding) {
243
        String locationPath = location.replaceAll("^(file:)?/*(.*)$", "$2");
1✔
244

245
        Source source = null;
1✔
246
        try {
247
            final Path p;
248
            if (contextPath == null) {
1✔
249
                p = Paths.get(locationPath);
1✔
250
            } else {
1✔
251
                p = Paths.get(contextPath, locationPath);
1✔
252
            }
253

254
            if (Files.isReadable(p)) {
1✔
255
                locationPath = p.toUri().toASCIIString();
1✔
256
                source = new FileSource(p, checkXQEncoding);
1✔
257
            }
258
        } catch (final InvalidPathException e) {
1✔
259
            // continue trying
260
        }
261

262
        if (source == null) {
1✔
263
            try {
264
                final Path p2 = Paths.get(locationPath);
1✔
265
                if (Files.isReadable(p2)) {
1!
266
                    locationPath = p2.toUri().toASCIIString();
×
267
                    source = new FileSource(p2, checkXQEncoding);
×
268
                }
269
            } catch (final InvalidPathException e) {
×
270
                // continue trying
271
            }
272
        }
273

274
        if (source == null && contextPath != null) {
1✔
275
            try {
276
                final Path p3 = Paths.get(contextPath).toAbsolutePath().resolve(locationPath);
1✔
277
                if (Files.isReadable(p3)) {
1!
278
                    locationPath = p3.toUri().toASCIIString();
×
279
                    source = new FileSource(p3, checkXQEncoding);
×
280
                }
281
            } catch (final InvalidPathException e) {
×
282
                // continue trying
283
            }
284
        }
285

286
        if (source == null) {
1✔
287
            /*
288
             * Try to load as an absolute path
289
             */
290
            try {
291
                final Path p4 = Paths.get("/" + locationPath);
1✔
292
                if (Files.isReadable(p4)) {
1✔
293
                    locationPath = p4.toUri().toASCIIString();
1✔
294
                    source = new FileSource(p4, checkXQEncoding);
1✔
295
                }
296
            } catch (final InvalidPathException e) {
1✔
297
                // continue trying
298
            }
299
        }
300

301
        if (source == null && contextPath != null) {
1✔
302
            /*
303
             * Try to load from the folder of the contextPath
304
             */
305
            try {
306
                final Path p5 = Paths.get(contextPath).resolveSibling(locationPath);
1✔
307
                if (Files.isReadable(p5)) {
1✔
308
                    locationPath = p5.toUri().toASCIIString();
1✔
309
                    source = new FileSource(p5, checkXQEncoding);
1✔
310
                }
311
            } catch (final InvalidPathException e) {
1✔
312
                // continue trying
313
            }
314
        }
315

316
        if (source == null && contextPath != null) {
1✔
317
            /*
318
             * Try to load from the parent folder of the contextPath URL
319
             */
320
            try {
321
                Path p6 = null;
1✔
322
                if(contextPath.startsWith("file:/")) {
1✔
323
                    try {
324
                        p6 = Paths.get(new URI(contextPath)).resolveSibling(locationPath);
1✔
325
                    } catch (final URISyntaxException e) {
1✔
326
                        // continue trying
327
                    }
328
                }
329

330
                if(p6 == null) {
1✔
331
                    p6 = Paths.get(contextPath.replaceFirst("^file:/*(/.*)$", "$1")).resolveSibling(locationPath);
1✔
332
                }
333

334
                if (Files.isReadable(p6)) {
1✔
335
                    locationPath = p6.toUri().toASCIIString();
1✔
336
                    source = new FileSource(p6, checkXQEncoding);
1✔
337
                }
338
            } catch (final InvalidPathException e) {
1✔
339
                // continue trying
340
            }
341
        }
342

343
        if (source == null && contextPath != null) {
1✔
344
            /*
345
             * Try to load from the contextPath URL folder
346
             */
347
            try {
348
                Path p7 = null;
1✔
349
                if(contextPath.startsWith("file:/")) {
1✔
350
                    try {
351
                        p7 = Paths.get(new URI(contextPath)).resolve(locationPath);
1✔
352
                    } catch (final URISyntaxException e) {
1✔
353
                        // continue trying
354
                    }
355
                }
356

357
                if(p7 == null) {
1✔
358
                    p7 = Paths.get(contextPath.replaceFirst("^file:/*(/.*)$", "$1")).resolve(locationPath);
1✔
359
                }
360

361
                if (Files.isReadable(p7)) {
1✔
362
                    locationPath = p7.toUri().toASCIIString();
1✔
363
                    source = new FileSource(p7, checkXQEncoding);
1✔
364
                }
365
            } catch (final InvalidPathException e) {
1✔
366
                // continue trying
367
            }
368
        }
369

370
        if (source == null) {
1✔
371
            /*
372
             * Lastly we try to load it using EXIST_HOME as the reference point
373
             */
374
            Path p8 = null;
1✔
375
            try {
376
                p8 = FileUtils.resolve(BrokerPool.getInstance().getConfiguration().getExistHome(), locationPath);
1✔
377
                if (Files.isReadable(p8)) {
1!
378
                    locationPath = p8.toUri().toASCIIString();
×
379
                    source = new FileSource(p8, checkXQEncoding);
×
380
                }
381
            } catch (final EXistException e) {
1✔
382
                LOG.warn(e);
1✔
383
            } catch (final InvalidPathException e) {
×
384
                // continue and abort below
385
            }
386
        }
387

388
        return source;
1✔
389
    }
390
}
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