• 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

63.97
/exist-core/src/main/java/org/exist/xslt/EXistURIResolver.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.xslt;
50

51
import java.io.IOException;
52
import java.lang.reflect.Array;
53
import java.net.URI;
54
import java.net.URISyntaxException;
55
import java.net.URL;
56
import javax.annotation.Nullable;
57
import javax.xml.transform.Source;
58
import javax.xml.transform.TransformerException;
59
import javax.xml.transform.URIResolver;
60
import javax.xml.transform.stream.StreamSource;
61
import org.apache.logging.log4j.LogManager;
62
import org.apache.logging.log4j.Logger;
63
import org.exist.dom.persistent.BinaryDocument;
64
import org.exist.dom.persistent.DocumentImpl;
65
import org.exist.security.Permission;
66
import org.exist.security.PermissionDeniedException;
67
import org.exist.storage.BrokerPool;
68
import org.exist.storage.DBBroker;
69
import org.exist.storage.txn.TransactionException;
70
import org.exist.storage.txn.Txn;
71
import org.exist.xmldb.XmldbURI;
72

73
/**
74
 * Implementation of URIResolver which
75
 * will resolve paths from the Elemental database
76
 * if they cannot be found we can optionally fallback
77
 * to returning a StreamSource from a URL
78
 */
79
public class EXistURIResolver implements URIResolver {
80

81
  private static final Logger LOG = LogManager.getLogger(EXistURIResolver.class);
1✔
82

83
  final BrokerPool db;
84
  final String basePath;
85
  final boolean fallbackToUrlStreamSource;
86

87
  public EXistURIResolver(final BrokerPool db, final String docPath) {
88
    this(db, docPath, false);
×
89
  }
×
90

91
  public EXistURIResolver(final BrokerPool db, final String docPath, final boolean fallbackToUrlStreamSource) {
1✔
92
    this.db = db;
1✔
93
    this.basePath = normalize(docPath);
1✔
94
    this.fallbackToUrlStreamSource = fallbackToUrlStreamSource;
1✔
95
    if (LOG.isDebugEnabled()) {
1!
96
      LOG.debug("EXistURIResolver base path set to {}", basePath);
×
97
    }
98
  }
1✔
99

100
  private String normalize(String uri) {
101
    if (uri.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX)) {
1✔
102
      return uri.substring(XmldbURI.EMBEDDED_SERVER_URI_PREFIX.length());
1✔
103
    }
104

105
    return uri;
1✔
106
  }
107

108
  /**
109
   * Simplify a path removing any "." and ".." path elements.
110
   * Assumes an absolute path is given.
111
   */
112
  private String normalizePath(final String path) {
113
    if (!path.startsWith("/")) {
1!
114
      throw new IllegalArgumentException("normalizePath may only be applied to an absolute path; " +
×
115
          "argument was: " + path + "; base: " + basePath);
×
116
    }
117

118
    final String[] pathComponents = path.substring(1).split("/");
1✔
119

120
    final int numPathComponents = Array.getLength(pathComponents);
1✔
121
    final String[] simplifiedComponents = new String[numPathComponents];
1✔
122
    int numSimplifiedComponents = 0;
1✔
123

124
    for (final String s : pathComponents) {
1✔
125
      // Remove empty elements ("/")
126
      if (s.isEmpty()) {
1!
127
        continue;
×
128
      }
129
      // Remove identity elements (".")
130
      if (".".equals(s)) {
1!
131
        continue;
×
132
      }
133
      // Remove parent elements ("..") unless at the root
134
      if ("..".equals(s)) {
1✔
135
        if (numSimplifiedComponents > 0) {
1!
136
          numSimplifiedComponents--;
1✔
137
        }
138
        continue;
1✔
139
      }
140
      simplifiedComponents[numSimplifiedComponents++] = s;
1✔
141
    }
142

143
    if (numSimplifiedComponents == 0) {
1!
144
      return "/";
×
145
    }
146

147
    final StringBuilder sb = new StringBuilder(path.length());
1✔
148
    for (int x = 0; x < numSimplifiedComponents; x++) {
1✔
149
      sb.append("/").append(simplifiedComponents[x]);
1✔
150
    }
151

152
    if (path.endsWith("/")) {
1!
153
      sb.append("/");
×
154
    }
155

156
    return sb.toString();
1✔
157
  }
158

159
  @Override
160
  public @Nullable Source resolve(final String href, String base) throws TransformerException {
161
    String path;
162

163
    if (href.isEmpty()) {
1!
164
      path = base;
×
165
    } else {
×
166
      URI hrefURI = null;
1✔
167
      try {
168
        hrefURI = new URI(href);
1✔
169
      } catch (final URISyntaxException e) {
1✔
170
      }
171
      if (hrefURI != null && hrefURI.isAbsolute()) {
1!
172
        path = href;
1✔
173
      } else {
1✔
174
        if (href.startsWith("/")) {
1✔
175
          path = href;
1✔
176
        } else if (href.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX)) {
1!
177
          path = href.substring(XmldbURI.EMBEDDED_SERVER_URI_PREFIX.length());
×
178
        } else if (base == null || base.length() == 0) {
1!
179
          path = basePath + "/" + href;
×
180
        } else {
×
181
          // Maybe base never contains this prefix?  Check to be sure.
182
          if (base.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX)) {
1!
183
            base = base.substring(XmldbURI.EMBEDDED_SERVER_URI_PREFIX.length());
×
184
          }
185
          path = base.substring(0, base.lastIndexOf('/') + 1) + href;
1✔
186
        }
187
      }
188
    }
189
    if (LOG.isDebugEnabled()) {
1!
190
      LOG.debug("Resolving path {} with base {} to {}", href, base, path);
×
191
      // + " (URI = " + uri.toASCIIString() + ")");
192
    }
193

194
    if (path.startsWith("/")) {
1✔
195
      path = normalizePath(path);
1✔
196
      return databaseSource(path);
1✔
197

198
    } else if (fallbackToUrlStreamSource) {
1!
199
      return urlSource(path);
×
200

201
    } else {
202
      return null;
1✔
203
    }
204
  }
205

206
  private Source urlSource(final String path) throws TransformerException {
207
    try {
208
      final URL url = new URL(path);
×
209
      return new StreamSource(url.openStream());
×
210
    } catch (final IOException e) {
×
211
      throw new TransformerException(e.getMessage(), e);
×
212
    }
213
  }
214

215
  private Source databaseSource(final String path) throws TransformerException {
216
    final XmldbURI uri = XmldbURI.create(path);
1✔
217

218
    final DBBroker broker = db.getActiveBroker();
1✔
219

220
    final DocumentImpl doc;
221
    try {
222
      doc = broker.getResource(uri, Permission.READ);
1✔
223
      if (doc == null) {
1!
224
        LOG.error("Document {} not found", path);
×
225
        throw new TransformerException("Resource " + path + " not found in database.");
×
226
      }
227

228
      final Source source;
229
      if (doc instanceof BinaryDocument) {
1!
230

231
        /*
232
         * NOTE: this is extremely unpleasant as we let a reference to the blob file
233
         * escape from the closure into the StreamSource. This means that the file could have been deleted
234
         * by time the user comes to access the StreamSource, however this was also
235
         * the case with the previous design, and due to the lack of resource
236
         * management of the StreamSource class, there is little we can do to improve
237
         * the situation - AR.
238
         */
239
        try (final Txn transaction = broker.getBrokerPool().getTransactionManager().beginTransaction()) {
×
240
          source = broker.withBinaryFile(transaction, (BinaryDocument) doc, p -> {
×
241
            final StreamSource source1 = new StreamSource(p.toFile());
×
242
            source1.setSystemId(p.toUri().toString());
×
243
            return source1;
×
244
          });
245

246
          transaction.commit();
×
247

248
          return source;
×
249
        }
250

251
      } else {
252
        source = new EXistDbSource(broker, doc);
1✔
253
        source.setSystemId(uri.toASCIIString());
1✔
254
        return source;
1✔
255
      }
256
    } catch (final PermissionDeniedException | TransactionException | IOException e) {
×
257
      throw new TransformerException(e.getMessage(), e);
×
258
    }
259
  }
260
}
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