• 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

0.0
/exist-core/src/main/java/org/exist/collections/triggers/XQueryStartupTrigger.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.collections.triggers;
50

51
import java.util.*;
52

53
import org.apache.logging.log4j.LogManager;
54
import org.apache.logging.log4j.Logger;
55
import org.exist.collections.Collection;
56
import org.exist.dom.persistent.DocumentImpl;
57
import org.exist.security.Permission;
58
import org.exist.security.PermissionDeniedException;
59
import org.exist.security.PermissionFactory;
60
import org.exist.security.SecurityManager;
61
import org.exist.source.Source;
62
import org.exist.source.SourceFactory;
63
import org.exist.storage.DBBroker;
64
import org.exist.storage.StartupTrigger;
65
import org.exist.storage.lock.Lock.LockMode;
66
import org.exist.storage.txn.TransactionManager;
67
import org.exist.storage.txn.Txn;
68
import org.exist.xmldb.XmldbURI;
69
import org.exist.xquery.CompiledXQuery;
70
import org.exist.xquery.XQuery;
71
import org.exist.xquery.XQueryContext;
72
import org.exist.xquery.value.Sequence;
73

74
import static org.exist.util.StringUtil.endsWith;
75
import static org.exist.util.StringUtil.substringBeforeLast;
76

77
/**
78
 * Startup Trigger to fire XQuery scripts during database startup.
79
 *
80
 * Load scripts into /db/system/autostart as DBA.
81
 *
82
 * <pre>
83
 * {@code
84
 * <startup>
85
 *   <triggers>
86
 *     <trigger class="org.exist.collections.triggers.XQueryStartupTrigger"/>
87
 *   </triggers>
88
 * </startup>
89
 * }
90
 * </pre>
91
 *
92
 * Due to security reasons individual scripts cannot be specified anymore. The permissions were not checked per file.
93
 *
94
 * <pre>
95
 * {@code
96
 *       <parameter name="xquery" value="/db/script1.xq"/>
97
 *       <parameter name="xquery" value="/db/script2.xq"/>
98
 * }
99
 * </pre>
100
 *
101
 * @author Dannes Wessels
102
 */
103
public class XQueryStartupTrigger implements StartupTrigger {
×
104

105
    protected final static Logger LOG = LogManager.getLogger(XQueryStartupTrigger.class);
×
106

107
    private static final String XQUERY = "xquery";
108
    private static final String AUTOSTART_COLLECTION = "/db/system/autostart";
109
    private static final String[] XQUERY_EXTENSIONS = {".xq", ".xquery", ".xqy"};
×
110
    private static final String REQUIRED_MIMETYPE = "application/xquery";
×
111

112
    @Override
113
    public void execute(DBBroker broker, final Txn transaction, Map<String, List<? extends Object>> params) {
114

115
        LOG.info("Starting Startup Trigger for stored XQueries");
×
116

117
        for (String path : getScriptsInStartupCollection(broker)) {
×
118
            executeQuery(broker, path);
×
119
        }
120

121
//        for (String path : getParameters(params)) {
122
//            executeQuery(broker, path);
123
//        }
124
    }
×
125

126
    /**
127
     * List all xquery scripts in /db/system/autostart
128
     *
129
     * @param broker The exist-db broker
130
     * @return List of xquery scripts
131
     */
132
    private List<String> getScriptsInStartupCollection(DBBroker broker) {
133

134
        // Return values
135
        List<String> paths = new ArrayList<>();
×
136

137
        XmldbURI uri = XmldbURI.create(AUTOSTART_COLLECTION);
×
138

139
        try(final Collection collection = broker.openCollection(uri, LockMode.READ_LOCK)) {
×
140
            if (collection == null) {
×
141
                LOG.debug("Collection {}' not found.", AUTOSTART_COLLECTION);
×
142
                createAutostartCollection(broker);
×
143

144
            } else {
×
145
                LOG.debug("Scanning collection '{}'.", AUTOSTART_COLLECTION);
×
146

147
                if (isPermissionsOK(collection)) {
×
148

149
                    Iterator<DocumentImpl> documents = collection.iteratorNoLock(broker);
×
150
                    while (documents.hasNext()) {
×
151
                        DocumentImpl document = documents.next();
×
152
                        String docPath = document.getURI().toString();
×
153

154
                        if (isPermissionsOK(document)) {
×
155

156
                            if (endsWith(docPath, XQUERY_EXTENSIONS)) {
×
157
                                paths.add(XmldbURI.EMBEDDED_SERVER_URI_PREFIX + docPath);
×
158

159
                            } else {
×
160
                                LOG.error("Skipped document '{}', not an xquery script.", docPath);
×
161
                            }
162

163
                        } else {
×
164
                            LOG.error("Document {} should be owned by DBA, mode {}, mimetype {}",
×
165
                                    docPath, Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM, REQUIRED_MIMETYPE);
×
166
                        }
167
                    }
168

169
                } else {
×
170
                    LOG.error("Collection {} should be owned by SYSTEM/DBA, mode {}.", AUTOSTART_COLLECTION,
×
171
                            Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM);
×
172
                }
173

174
            }
175

176
            LOG.debug("Found {} XQuery scripts in '{}'.", paths.size(), AUTOSTART_COLLECTION);
×
177

178
        } catch (PermissionDeniedException ex) {
×
179
            LOG.error(ex.getMessage());
×
180
        }
181

182
        return paths;
×
183

184
    }
185

186
    /**
187
     * Verify that the permissions for a collection are SYSTEM/DBA/770
188
     *
189
     * @param collection The collection
190
     * @return TRUE if the conditions are met, else FALSE
191
     */
192
    private boolean isPermissionsOK(Collection collection) {
193

194
        Permission perms = collection.getPermissions();
×
195

196
        return (perms.getOwner().getName().equals(SecurityManager.SYSTEM)
×
197
                && perms.getGroup().getName().equals(SecurityManager.DBA_GROUP)
×
198
                && perms.getMode() == Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM);
×
199

200
    }
201

202
    /**
203
     * Verify that the owner of the document is DBA, the document is owned by the DBA group and that the permissions are
204
     * set 0770, and the mimetype is set application/xquery.
205
     *
206
     * @param document The document
207
     * @return TRUE if the conditions are met, else FALSE
208
     */
209
    private boolean isPermissionsOK(final DocumentImpl document) {
210
        final Permission perms = document.getPermissions();
×
211
        return (perms.getOwner().hasDbaRole()
×
212
                && perms.getGroup().getName().equals(SecurityManager.DBA_GROUP)
×
213
                && perms.getMode() == Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM
×
214
                && document.getMimeType().equals(REQUIRED_MIMETYPE));
×
215

216
    }
217

218
    /**
219
     * Get all XQuery paths from provided parameters in conf.xml
220
     */
221
    private List<String> getParameters(Map<String, List<? extends Object>> params) {
222

223
        // Return values
224
        List<String> paths = new ArrayList<>();
×
225

226
        // The complete data map
227
        Set<Map.Entry<String, List<? extends Object>>> data = params.entrySet();
×
228

229
        // Iterate over all entries
230
        for (Map.Entry<String, List<? extends Object>> entry : data) {
×
231

232
            // only the 'xpath' parameter is used.
233
            if (XQUERY.equals(entry.getKey())) {
×
234

235
                // Iterate over all values (object lists)
236
                List<? extends Object> list = entry.getValue();
×
237
                for (Object o : list) {
×
238

239
                    if (o instanceof String value) {
×
240

241
                        if (value.startsWith("/")) {
×
242

243
                            // Rewrite to URL in database
244
                            value = XmldbURI.EMBEDDED_SERVER_URI_PREFIX + value;
×
245

246
                            // Prevent double entries
247
                            if (!paths.contains(value)) {
×
248
                                paths.add(value);
×
249
                            }
250

251
                        } else {
×
252
                            LOG.error("Path '{}' should start with a '/'", value);
×
253
                        }
254
                    }
255
                }
256
            }
257

258
        }
259

260
        LOG.debug("Found {} 'xquery' entries.", paths.size());
×
261

262
        return paths;
×
263
    }
264

265
    /**
266
     * Execute xquery on path
267
     *
268
     * @param broker eXist database broker
269
     * @param path path to query, formatted as xmldb:exist:///db/...
270
     */
271
    private void executeQuery(DBBroker broker, String path) {
272

273
        XQueryContext context = null;
×
274
        try {
275
            // Get path to xquery
276
            Source source = SourceFactory.getSource(broker, null, path, false);
×
277

278
            if (source == null) {
×
279
                LOG.info("No XQuery found at '{}'", path);
×
280

281
            } else {
×
282
                // Setup xquery service
283
                XQuery service = broker.getBrokerPool().getXQueryService();
×
284
                context = new XQueryContext(broker.getBrokerPool());
×
285

286
                // Allow use of modules with relative paths
287
                String moduleLoadPath = substringBeforeLast(path, "/");
×
288
                context.setModuleLoadPath(moduleLoadPath);
×
289

290
                // Compile query
291
                CompiledXQuery compiledQuery = service.compile(context, source);
×
292

293
                LOG.info("Starting XQuery at '{}'", path);
×
294

295
                // Finish preparation
296
                context.prepareForExecution();
×
297

298
                // Execute
299
                Sequence result = service.execute(broker, compiledQuery, null);
×
300

301
                // Log results
302
                LOG.info("Result XQuery: '{}'", result.getStringValue());
×
303

304
            }
305

306
        } catch (Throwable t) {
×
307
            // Dirty, catch it all
308
            LOG.error("An error occurred during preparation/execution of the XQuery script {}: {}", path, t.getMessage(), t);
×
309

310
        } finally {
311
            if (context != null) {
×
312
                context.runCleanupTasks();
×
313
            }
314
        }
315
    }
×
316

317
    /**
318
     * Create autostart collection when not existent
319
     *
320
     * @param broker The exist-db broker
321
     */
322
    private void createAutostartCollection(DBBroker broker) {
323

324
        LOG.info("Creating {}", AUTOSTART_COLLECTION);
×
325

326
        final TransactionManager txnManager = broker.getBrokerPool().getTransactionManager();
×
327
        try(final Txn txn = txnManager.beginTransaction()) {
×
328
            XmldbURI newCollection = XmldbURI.create(AUTOSTART_COLLECTION, true);
×
329

330
            // Create collection
331
            final Collection created = broker.getOrCreateCollection(txn, newCollection);
×
332

333
            // Set ownership and mode
334
            PermissionFactory.chown(broker, created, Optional.of(SecurityManager.SYSTEM), Optional.of(SecurityManager.DBA_GROUP));
×
335
            PermissionFactory.chmod(broker, created, Optional.of(Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM), Optional.empty());
×
336

337
            broker.saveCollection(txn, created);
×
338
            broker.flush();
×
339

340
            // Commit change
341
            txnManager.commit(txn);
×
342

343
            if (LOG.isDebugEnabled()) {
×
344
                LOG.debug("Finished creation of collection");
×
345
            }
346

347
        } catch (Throwable ex) {
×
348
            LOG.error(ex);
×
349
        }
350
    }
×
351

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