• 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

65.08
/exist-core/src/main/java/org/exist/util/Configuration.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.util;
50

51
import com.evolvedbinary.j8fu.tuple.Tuple2;
52
import org.apache.logging.log4j.LogManager;
53
import org.apache.logging.log4j.Logger;
54

55
import org.exist.backup.SystemExport;
56
import org.exist.collections.CollectionCache;
57
import org.exist.repo.Deployment;
58

59
import org.exist.resolver.ResolverFactory;
60
import org.exist.start.Main;
61
import org.exist.storage.BrokerPoolConstants;
62
import org.exist.storage.lock.LockManager;
63
import org.exist.storage.lock.LockTable;
64
import org.exist.util.io.ContentFilePool;
65
import org.exist.xquery.Expression;
66
import org.exist.xquery.PerformanceStats;
67
import org.exist.xquery.XQueryWatchDog;
68
import org.w3c.dom.Document;
69
import org.w3c.dom.Element;
70
import org.w3c.dom.Node;
71
import org.w3c.dom.NodeList;
72

73
import org.xml.sax.ErrorHandler;
74
import org.xml.sax.InputSource;
75
import org.xml.sax.SAXException;
76
import org.xml.sax.SAXParseException;
77
import org.xml.sax.XMLReader;
78

79
import org.exist.Indexer;
80
import org.exist.indexing.IndexManager;
81
import org.exist.dom.memtree.SAXAdapter;
82
import org.exist.scheduler.JobConfig;
83
import org.exist.scheduler.JobException;
84
import org.exist.storage.DBBroker;
85
import org.exist.storage.DefaultCacheManager;
86
import org.exist.storage.IndexSpec;
87
import org.exist.storage.NativeBroker;
88
import org.exist.storage.XQueryPool;
89
import org.exist.storage.serializers.CustomMatchListenerFactory;
90
import org.exist.storage.serializers.Serializer;
91
import org.exist.validation.GrammarPool;
92
import org.exist.xmldb.DatabaseImpl;
93
import org.exist.xslt.TransformerFactoryAllocator;
94

95
import java.io.IOException;
96
import java.io.InputStream;
97

98
import java.net.URISyntaxException;
99
import java.nio.file.Files;
100
import java.nio.file.Path;
101
import java.nio.file.Paths;
102
import java.util.ArrayList;
103
import java.util.List;
104
import java.util.Locale;
105
import java.util.HashMap;
106
import java.util.Map;
107
import java.util.Map.Entry;
108
import java.util.Optional;
109
import java.util.Properties;
110
import java.util.function.Function;
111

112
import javax.annotation.Nullable;
113
import javax.xml.parsers.ParserConfigurationException;
114
import javax.xml.parsers.SAXParser;
115
import javax.xml.parsers.SAXParserFactory;
116

117
import org.exist.Namespaces;
118
import org.exist.scheduler.JobType;
119
import org.xmlresolver.Resolver;
120

121
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
122
import static java.lang.Boolean.FALSE;
123
import static java.lang.Boolean.TRUE;
124
import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
125
import static org.exist.Namespaces.XPATH_FUNCTIONS_NS;
126
import static org.exist.scheduler.JobConfig.JOB_CLASS_ATTRIBUTE;
127
import static org.exist.scheduler.JobConfig.JOB_CRON_TRIGGER_ATTRIBUTE;
128
import static org.exist.scheduler.JobConfig.JOB_DELAY_ATTRIBUTE;
129
import static org.exist.scheduler.JobConfig.JOB_NAME_ATTRIBUTE;
130
import static org.exist.scheduler.JobConfig.JOB_PERIOD_ATTRIBUTE;
131
import static org.exist.scheduler.JobConfig.JOB_REPEAT_ATTRIBUTE;
132
import static org.exist.scheduler.JobConfig.JOB_TYPE_ATTRIBUTE;
133
import static org.exist.scheduler.JobConfig.JOB_UNSCHEDULE_ON_EXCEPTION;
134
import static org.exist.scheduler.JobConfig.JOB_XQUERY_ATTRIBUTE;
135
import static org.exist.scheduler.JobConfig.PROPERTY_SCHEDULER_JOBS;
136
import static org.exist.storage.BrokerFactory.PROPERTY_DATABASE;
137
import static org.exist.Indexer.CONFIGURATION_INDEX_ELEMENT_NAME;
138
import static org.exist.Indexer.PRESERVE_WS_MIXED_CONTENT_ATTRIBUTE;
139
import static org.exist.Indexer.PROPERTY_INDEXER_CONFIG;
140
import static org.exist.Indexer.PROPERTY_PRESERVE_WS_MIXED_CONTENT;
141
import static org.exist.Indexer.PROPERTY_SUPPRESS_WHITESPACE;
142
import static org.exist.Indexer.SUPPRESS_WHITESPACE_ATTRIBUTE;
143
import static org.exist.collections.CollectionCache.PROPERTY_CACHE_SIZE_BYTES;
144
import static org.exist.storage.BrokerPoolConstants.CONFIGURATION_CONNECTION_ELEMENT_NAME;
145
import static org.exist.storage.BrokerPoolConstants.DATA_DIR_ATTRIBUTE;
146
import static org.exist.storage.BrokerPoolConstants.DISK_SPACE_MIN_PROPERTY;
147
import static org.exist.storage.BrokerPoolConstants.MAX_CONNECTIONS_ATTRIBUTE;
148
import static org.exist.storage.BrokerPoolConstants.MIN_CONNECTIONS_ATTRIBUTE;
149
import static org.exist.storage.BrokerPoolConstants.PROPERTY_COLLECTION_CACHE_SIZE;
150
import static org.exist.storage.BrokerPoolConstants.PROPERTY_DATA_DIR;
151
import static org.exist.storage.BrokerPoolConstants.PROPERTY_MAX_CONNECTIONS;
152
import static org.exist.storage.BrokerPoolConstants.PROPERTY_MIN_CONNECTIONS;
153
import static org.exist.storage.BrokerPoolConstants.PROPERTY_NODES_BUFFER;
154
import static org.exist.storage.BrokerPoolConstants.PROPERTY_PAGE_SIZE;
155
import static org.exist.storage.BrokerPoolConstants.PROPERTY_RECOVERY_CHECK;
156
import static org.exist.storage.BrokerPoolConstants.PROPERTY_RECOVERY_ENABLED;
157
import static org.exist.storage.BrokerPoolConstants.PROPERTY_RECOVERY_FORCE_RESTART;
158
import static org.exist.storage.BrokerPoolConstants.PROPERTY_RECOVERY_GROUP_COMMIT;
159
import static org.exist.storage.BrokerPoolConstants.PROPERTY_SHUTDOWN_DELAY;
160
import static org.exist.storage.BrokerPoolConstants.PROPERTY_STARTUP_TRIGGERS;
161
import static org.exist.storage.BrokerPoolConstants.PROPERTY_SYNC_PERIOD;
162
import static org.exist.storage.BrokerPoolConstants.RECOVERY_ENABLED_ATTRIBUTE;
163
import static org.exist.storage.BrokerPoolConstants.RECOVERY_FORCE_RESTART_ATTRIBUTE;
164
import static org.exist.storage.BrokerPoolConstants.RECOVERY_GROUP_COMMIT_ATTRIBUTE;
165
import static org.exist.storage.BrokerPoolConstants.RECOVERY_POST_RECOVERY_CHECK;
166
import static org.exist.storage.BrokerPoolConstants.SHUTDOWN_DELAY_ATTRIBUTE;
167
import static org.exist.storage.BrokerPoolConstants.SYNC_PERIOD_ATTRIBUTE;
168
import static org.exist.storage.DBBroker.POSIX_CHOWN_RESTRICTED_ATTRIBUTE;
169
import static org.exist.storage.DBBroker.POSIX_CHOWN_RESTRICTED_PROPERTY;
170
import static org.exist.storage.DBBroker.PRESERVE_ON_COPY_ATTRIBUTE;
171
import static org.exist.storage.DBBroker.PRESERVE_ON_COPY_PROPERTY;
172
import static org.exist.storage.DBBroker.PROPERTY_XUPDATE_CONSISTENCY_CHECKS;
173
import static org.exist.storage.DBBroker.PROPERTY_XUPDATE_FRAGMENTATION_FACTOR;
174
import static org.exist.storage.DBBroker.PreserveType;
175
import static org.exist.storage.DBBroker.XUPDATE_CONSISTENCY_CHECKS_ATTRIBUTE;
176
import static org.exist.storage.DBBroker.XUPDATE_FRAGMENTATION_FACTOR_ATTRIBUTE;
177
import static org.exist.storage.DefaultCacheManager.CACHE_CHECK_MAX_SIZE_ATTRIBUTE;
178
import static org.exist.storage.DefaultCacheManager.CACHE_SIZE_ATTRIBUTE;
179
import static org.exist.storage.DefaultCacheManager.DEFAULT_CACHE_CHECK_MAX_SIZE_STRING;
180
import static org.exist.storage.DefaultCacheManager.PROPERTY_CACHE_CHECK_MAX_SIZE;
181
import static org.exist.storage.DefaultCacheManager.PROPERTY_CACHE_SIZE;
182
import static org.exist.storage.DefaultCacheManager.SHRINK_THRESHOLD_ATTRIBUTE;
183
import static org.exist.storage.DefaultCacheManager.SHRINK_THRESHOLD_PROPERTY;
184
import static org.exist.storage.NativeBroker.INDEX_DEPTH_ATTRIBUTE;
185
import static org.exist.storage.NativeBroker.PROPERTY_INDEX_DEPTH;
186
import static org.exist.storage.NativeValueIndex.INDEX_CASE_SENSITIVE_ATTRIBUTE;
187
import static org.exist.storage.NativeValueIndex.PROPERTY_INDEX_CASE_SENSITIVE;
188
import static org.exist.storage.XQueryPool.MAX_STACK_SIZE_ATTRIBUTE;
189
import static org.exist.storage.XQueryPool.POOL_SIZE_ATTTRIBUTE;
190
import static org.exist.storage.XQueryPool.PROPERTY_MAX_STACK_SIZE;
191
import static org.exist.storage.journal.Journal.PROPERTY_RECOVERY_JOURNAL_DIR;
192
import static org.exist.storage.journal.Journal.PROPERTY_RECOVERY_SIZE_LIMIT;
193
import static org.exist.storage.journal.Journal.PROPERTY_RECOVERY_SYNC_ON_COMMIT;
194
import static org.exist.storage.journal.Journal.RECOVERY_JOURNAL_DIR_ATTRIBUTE;
195
import static org.exist.storage.journal.Journal.RECOVERY_SIZE_LIMIT_ATTRIBUTE;
196
import static org.exist.storage.journal.Journal.RECOVERY_SYNC_ON_COMMIT_ATTRIBUTE;
197
import static org.exist.storage.serializers.Serializer.ADD_EXIST_ID_ATTRIBUTE;
198
import static org.exist.storage.serializers.Serializer.COMPRESS_OUTPUT_ATTRIBUTE;
199
import static org.exist.storage.serializers.Serializer.ENABLE_XINCLUDE_ATTRIBUTE;
200
import static org.exist.storage.serializers.Serializer.ENABLE_XSL_ATTRIBUTE;
201
import static org.exist.storage.serializers.Serializer.INDENT_ATTRIBUTE;
202
import static org.exist.storage.serializers.Serializer.OMIT_ORIGINAL_XML_DECLARATION_ATTRIBUTE;
203
import static org.exist.storage.serializers.Serializer.OMIT_XML_DECLARATION_ATTRIBUTE;
204
import static org.exist.storage.serializers.Serializer.OUTPUT_DOCTYPE_ATTRIBUTE;
205
import static org.exist.storage.serializers.Serializer.PROPERTY_ADD_EXIST_ID;
206
import static org.exist.storage.serializers.Serializer.PROPERTY_COMPRESS_OUTPUT;
207
import static org.exist.storage.serializers.Serializer.PROPERTY_ENABLE_XINCLUDE;
208
import static org.exist.storage.serializers.Serializer.PROPERTY_ENABLE_XSL;
209
import static org.exist.storage.serializers.Serializer.PROPERTY_INDENT;
210
import static org.exist.storage.serializers.Serializer.PROPERTY_OMIT_ORIGINAL_XML_DECLARATION;
211
import static org.exist.storage.serializers.Serializer.PROPERTY_OMIT_XML_DECLARATION;
212
import static org.exist.storage.serializers.Serializer.PROPERTY_OUTPUT_DOCTYPE;
213
import static org.exist.storage.serializers.Serializer.PROPERTY_TAG_MATCHING_ATTRIBUTES;
214
import static org.exist.storage.serializers.Serializer.PROPERTY_TAG_MATCHING_ELEMENTS;
215
import static org.exist.storage.serializers.Serializer.TAG_MATCHING_ATTRIBUTES_ATTRIBUTE;
216
import static org.exist.storage.serializers.Serializer.TAG_MATCHING_ELEMENTS_ATTRIBUTE;
217
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_CLASS_ATTRIBUTE;
218
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_ELEMENT;
219
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_FEATURES_ELEMENT;
220
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_FEATURES_PROPERTY;
221
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_PROPERTIES_ELEMENT;
222
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_PROPERTIES_PROPERTY;
223
import static org.exist.util.HtmlToXmlParser.HTML_TO_XML_PARSER_PROPERTY;
224
import static org.exist.util.ParametersExtractor.PARAMETER_ELEMENT_NAME;
225
import static org.exist.util.StringUtil.nullIfEmpty;
226
import static org.exist.util.XMLReaderObjectFactory.PROPERTY_VALIDATION_MODE;
227
import static org.exist.util.XMLReaderPool.XmlParser.XML_PARSER_ELEMENT;
228
import static org.exist.util.XMLReaderPool.XmlParser.XML_PARSER_FEATURES_ELEMENT;
229
import static org.exist.util.XMLReaderPool.XmlParser.XML_PARSER_FEATURES_PROPERTY;
230
import static org.exist.util.io.ContentFilePool.PROPERTY_IN_MEMORY_SIZE;
231
import static org.exist.util.io.VirtualTempPath.DEFAULT_IN_MEMORY_SIZE;
232
import static org.exist.xquery.FunctionFactory.DISABLE_DEPRECATED_FUNCTIONS_ATTRIBUTE;
233
import static org.exist.xquery.FunctionFactory.DISABLE_DEPRECATED_FUNCTIONS_BY_DEFAULT;
234
import static org.exist.xquery.FunctionFactory.ENABLE_JAVA_BINDING_ATTRIBUTE;
235
import static org.exist.xquery.FunctionFactory.PROPERTY_DISABLE_DEPRECATED_FUNCTIONS;
236
import static org.exist.xquery.FunctionFactory.PROPERTY_ENABLE_JAVA_BINDING;
237
import static org.exist.xquery.XQueryContext.BUILT_IN_MODULE_CLASS_ATTRIBUTE;
238
import static org.exist.xquery.XQueryContext.BUILT_IN_MODULE_SOURCE_ATTRIBUTE;
239
import static org.exist.xquery.XQueryContext.BUILT_IN_MODULE_URI_ATTRIBUTE;
240
import static org.exist.xquery.XQueryContext.ENABLE_QUERY_REWRITING_ATTRIBUTE;
241
import static org.exist.xquery.XQueryContext.ENFORCE_INDEX_USE_ATTRIBUTE;
242
import static org.exist.xquery.XQueryContext.PROPERTY_BUILT_IN_MODULES;
243
import static org.exist.xquery.XQueryContext.PROPERTY_ENABLE_QUERY_REWRITING;
244
import static org.exist.xquery.XQueryContext.PROPERTY_ENFORCE_INDEX_USE;
245
import static org.exist.xquery.XQueryContext.PROPERTY_MODULE_PARAMETERS;
246
import static org.exist.xquery.XQueryContext.PROPERTY_STATIC_MODULE_MAP;
247
import static org.exist.xquery.XQueryContext.PROPERTY_XQUERY_BACKWARD_COMPATIBLE;
248
import static org.exist.xquery.XQueryContext.PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL;
249
import static org.exist.xquery.XQueryContext.XQUERY_BACKWARD_COMPATIBLE_ATTRIBUTE;
250
import static org.exist.xquery.XQueryContext.XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_ATTRIBUTE;
251
import static org.exist.xquery.XQueryContext.XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_DEFAULT;
252
import static org.exist.xquery.XQueryWatchDog.PROPERTY_OUTPUT_SIZE_LIMIT;
253
import static org.exist.xquery.XQueryWatchDog.PROPERTY_QUERY_TIMEOUT;
254
import static org.exist.xslt.TransformerFactoryAllocator.CONFIGURATION_TRANSFORMER_ATTRIBUTE_ELEMENT_NAME;
255
import static org.exist.xslt.TransformerFactoryAllocator.PROPERTY_CACHING_ATTRIBUTE;
256
import static org.exist.xslt.TransformerFactoryAllocator.PROPERTY_TRANSFORMER_ATTRIBUTES;
257
import static org.exist.xslt.TransformerFactoryAllocator.PROPERTY_TRANSFORMER_CLASS;
258
import static org.exist.xslt.TransformerFactoryAllocator.TRANSFORMER_CACHING_ATTRIBUTE;
259
import static org.exist.xslt.TransformerFactoryAllocator.TRANSFORMER_CLASS_ATTRIBUTE;
260

261

262
public class Configuration implements ErrorHandler {
263
    public static final String BINARY_CACHE_CLASS_PROPERTY = "binary.cache.class";
264
    private static final String PRP_DETAILS = "{}: {}";
265
    private static final Logger LOG = LogManager.getLogger(Configuration.class); //Logger
1✔
266
    private static final String XQUERY_CONFIGURATION_ELEMENT_NAME = "xquery";
267
    private static final String XQUERY_BUILTIN_MODULES_CONFIGURATION_MODULES_ELEMENT_NAME = "builtin-modules";
268
    private static final String XQUERY_BUILTIN_MODULES_CONFIGURATION_MODULE_ELEMENT_NAME = "module";
269
    private static final String CANNOT_CONVERT_VALUE_TO_INTEGER = "Cannot convert {} value to integer: {}";
270
    private static final String ORG_EXIST_STORAGE_STARTUP_TRIGGER = "org.exist.storage.StartupTrigger";
271
    private static final String ERROR_READING_CONFIGURATION_FILE_LINE = "Error occurred while reading configuration file [line: {}]:{}";
1✔
272

273
    private final Map<String, Object> config = new HashMap<>(); //Configuration
1✔
274

275
    protected Optional<Path> configFilePath = Optional.empty();
1✔
276
    protected Optional<Path> existHome = Optional.empty();
1✔
277

278
    public Configuration() throws DatabaseConfigurationException {
279
        this(DatabaseImpl.CONF_XML, Optional.empty());
1✔
280
    }
1✔
281

282
    public Configuration(@Nullable String configFilename) throws DatabaseConfigurationException {
283
        this(configFilename, Optional.empty());
1✔
284
    }
1✔
285

286
    public Configuration(@Nullable String configFilename, Optional<Path> existHomeDirname)
1✔
287
            throws DatabaseConfigurationException {
288
        InputStream is = null;
1✔
289
        try {
290
            if (configFilename == null) {
1✔
291
                // Default file name
292
                configFilename = DatabaseImpl.CONF_XML;
1✔
293
            }
294

295
            // firstly, try to read the configuration from a file within the
296
            // classpath
297
            try {
298
                is = Configuration.class.getClassLoader().getResourceAsStream(configFilename);
1✔
299

300
                if (is != null) {
1✔
301
                    LOG.info("Reading configuration from classloader");
1✔
302
                    configFilePath = Optional.of(Paths.get(Configuration.class.getClassLoader().getResource(configFilename).toURI()));
1✔
303
                }
304
            } catch (final Exception e) {
1✔
305
                // EB: ignore and go forward, e.g. in case there is an absolute
306
                // file name for configFileName
307
                LOG.debug(e);
×
308
            }
309

310
            existHomeDirname = existHomeDirname.map(Path::normalize);
1✔
311

312
            // otherwise, secondly try to read configuration from file. Guess the
313
            // location if necessary
314
            if (is == null) {
1✔
315
                existHome = existHomeDirname.map(Optional::of)
1✔
316
                        .orElse(ConfigurationHelper.getExistHome(configFilename));
1✔
317

318
                if (existHome.isEmpty()) {
1!
319

320
                    // EB: try to create existHome based on location of config file
321
                    // when config file points to absolute file location
322
                    final Path absoluteConfigFile = Paths.get(configFilename);
×
323

324
                    if (absoluteConfigFile.isAbsolute() && Files.exists(absoluteConfigFile) && Files.isReadable(absoluteConfigFile)) {
×
325
                        existHome = Optional.of(absoluteConfigFile.getParent());
×
326
                        configFilename = FileUtils.fileName(absoluteConfigFile);
×
327
                    }
328
                }
329

330
                Path configFile = Paths.get(configFilename);
1✔
331

332
                if (!configFile.isAbsolute() && existHome.isPresent()) {
1!
333

334
                    // try the passed or constructed existHome first
335
                    configFile = existHome.get().resolve(configFilename);
×
336

337
                    if (!Files.exists(configFile)) {
×
338
                        configFile = existHome.get().resolve(Main.CONFIG_DIR_NAME).resolve(configFilename);
×
339
                    }
340
                }
341

342
                if (!Files.exists(configFile) || !Files.isReadable(configFile)) {
1!
343
                    throw new DatabaseConfigurationException("Unable to read configuration file at " + configFile);
×
344
                }
345

346
                configFilePath = Optional.of(configFile.toAbsolutePath());
1✔
347
                is = Files.newInputStream(configFile);
1✔
348
            }
349

350
            LOG.info("Reading configuration from file {}", configFilePath.map(Path::toString).orElse("Unknown"));
1✔
351

352
            // set dbHome to parent of the conf file found, to resolve relative
353
            // path from conf file
354
            final Optional<Path> existHomePath = configFilePath.map(Path::getParent);
1✔
355

356
            // initialize xml parser
357
            // we use eXist's in-memory DOM implementation to work
358
            // around a bug in Xerces
359
            final SAXParserFactory factory = ExistSAXParserFactory.getSAXParserFactory();
1✔
360
            factory.setNamespaceAware(true);
1✔
361

362
            final InputSource src = new InputSource(is);
1✔
363
            final SAXParser parser = factory.newSAXParser();
1✔
364
            final XMLReader reader = parser.getXMLReader();
1✔
365

366
            reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
1✔
367
            reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
1✔
368
            reader.setFeature(FEATURE_SECURE_PROCESSING, true);
1✔
369

370
            final SAXAdapter adapter = new SAXAdapter((Expression) null);
1✔
371
            reader.setContentHandler(adapter);
1✔
372
            reader.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
1✔
373
            reader.parse(src);
1✔
374

375
            final Document doc = adapter.getDocument();
1✔
376

377
            //indexer settings
378
            configureElement(doc, Indexer.CONFIGURATION_ELEMENT_NAME, element -> configureIndexer(doc, element));
1✔
379
            //scheduler settings
380
            configureElement(doc, JobConfig.CONFIGURATION_ELEMENT_NAME, this::configureScheduler);
1✔
381
            //db connection settings
382
            configureElement(doc, CONFIGURATION_CONNECTION_ELEMENT_NAME, element -> configureBackend(existHomePath, element));
1✔
383
            // lock-table settings
384
            configureElement(doc, "lock-manager", this::configureLockManager);
1✔
385
            // repository settings
386
            configureElement(doc, "repository", this::configureRepository);
1✔
387
            // binary manager settings
388
            configureElement(doc, "binary-manager", this::configureBinaryManager);
1✔
389
            // transformer settings
390
            configureElement(doc, TransformerFactoryAllocator.CONFIGURATION_ELEMENT_NAME, this::configureTransformer);
1✔
391
            // saxon settings (most importantly license file for PE or EE features)
392
            configureElement(doc, SaxonConfiguration.SAXON_CONFIGURATION_ELEMENT_NAME, this::configureSaxon);
1✔
393
            // parser settings
394
            configureElement(doc, HtmlToXmlParser.PARSER_ELEMENT_NAME, this::configureParser);
1✔
395
            // serializer settings
396
            configureElement(doc, Serializer.CONFIGURATION_ELEMENT_NAME, this::configureSerializer);
1✔
397
            // XUpdate settings
398
            configureElement(doc, DBBroker.CONFIGURATION_ELEMENT_NAME, this::configureXUpdate);
1✔
399
            // XQuery settings
400
            configureElement(doc, XQUERY_CONFIGURATION_ELEMENT_NAME, this::configureXQuery);
1✔
401
            // Validation
402
            configureElement(doc, XMLReaderObjectFactory.CONFIGURATION_ELEMENT_NAME, element -> configureValidation(existHomePath, element));
1✔
403
            // RPC server
404
            configureElement(doc, "rpc-server", this::configureRpcServer);
1✔
405
        } catch (final SAXException | IOException | ParserConfigurationException e) {
1✔
406
            LOG.error("error while reading config file: {}", configFilename, e);
×
407
            throw new DatabaseConfigurationException(e.getMessage(), e);
×
408
        } finally {
409
            if (is != null) {
1!
410
                try {
411
                    is.close();
1✔
412
                } catch (final IOException ioe) {
1✔
413
                    LOG.error(ioe);
×
414
                }
415
            }
416
        }
417
    }
1✔
418

419
    @FunctionalInterface
420
    interface ElementConfigurationAction {
421
        void apply(Element element) throws DatabaseConfigurationException;
422
    }
423
    private void configureElement(final Document doc, final String elementName, final ElementConfigurationAction action)
424
            throws DatabaseConfigurationException {
425
        configureFirstElement(doc.getElementsByTagName(elementName), action);
1✔
426
    }
1✔
427

428
    private void configureElement(final Element parent, final String elementName, final ElementConfigurationAction action)
429
            throws DatabaseConfigurationException {
430
        configureFirstElement(parent.getElementsByTagName(elementName), action);
1✔
431
    }
1✔
432

433
    private void configureFirstElement(final NodeList nodeList, final ElementConfigurationAction action)
434
            throws DatabaseConfigurationException {
435
        if (nodeList.getLength() > 0) {
1✔
436
            action.apply((Element)nodeList.item(0));
1✔
437
        }
438
    }
1✔
439

440
    /**
441
     * Takes the passed string and converts it to a non-null <code>Boolean</code> object. If value is null, the specified default value is used.
442
     * Otherwise, Boolean.TRUE is returned if and only if the passed string equals &quot;yes&quot; or &quot;true&quot;, ignoring case.
443
     *
444
     * @param value        The string to parse
445
     * @param defaultValue The default if the string is null
446
     * @return The parsed <code>Boolean</code>
447
     */
448
    public static boolean parseBoolean(@Nullable final String value, final boolean defaultValue) {
449
        Boolean booleanValue = asBoolean(value);
1✔
450
        if (booleanValue == null)  {
1!
451
            return defaultValue;
×
452
        }
453
        return booleanValue.booleanValue();
1✔
454
    }
455

456
    @Nullable
457
    private static Boolean asBoolean(@Nullable final String value) {
458
        if (value != null) {
1!
459
            return Boolean.valueOf("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value));
1✔
460
        }
461
        return null;
×
462
    }
463

464
    @Nullable
465
    private static Integer asInteger(@Nullable final String value) {
466
        if (value != null) {
1!
467
            try {
468
                return Integer.valueOf(value);
1✔
469
            } catch (NumberFormatException nfe) {
×
470
                LOG.warn("Cannot convert {} value to Integer", value, nfe);
×
471
            }
472
        }
473
        return null;
×
474
    }
475

476
    @Nullable
477
    private static Long asLong(@Nullable final String value) {
478
        if (value != null) {
1!
479
            try {
480
                return Long.valueOf(value);
1✔
481
            } catch (NumberFormatException nfe) {
×
482
                LOG.warn("Cannot convert {} value to Long", value, nfe);
×
483
            }
484
        }
485
        return null;
×
486
    }
487

488
    /**
489
     * Takes the passed string and converts it to a non-null <code>int</code> value. If value is null, the specified default value is used.
490
     * Otherwise, Boolean.TRUE is returned if and only if the passed string equals &quot;yes&quot; or &quot;true&quot;, ignoring case.
491
     *
492
     * @param value        The string to parse
493
     * @param defaultValue The default if the string is null or empty
494
     * @return The parsed <code>int</code>
495
     */
496
    public static int parseInt(@Nullable final String value, final int defaultValue) {
497
        if (value == null || value.isEmpty()) {
1!
498
            return defaultValue;
×
499
        }
500

501
        try {
502
            return Integer.parseInt(value);
1✔
503
        } catch (final NumberFormatException e) {
×
504
            LOG.warn("Could not parse: {}, as an int: {}", value, e.getMessage());
×
505
            return defaultValue;
×
506
        }
507
    }
508

509
    private void configureLockManager(final Element lockManager) throws DatabaseConfigurationException {
510
        configureProperty(lockManager, "upgrade-check", LockManager.CONFIGURATION_UPGRADE_CHECK, Configuration::asBoolean, FALSE);
1✔
511
        configureProperty(lockManager, "warn-wait-on-read-for-write", LockManager.CONFIGURATION_WARN_WAIT_ON_READ_FOR_WRITE, Configuration::asBoolean, FALSE);
1✔
512
        configureProperty(lockManager, "paths-multi-writer", LockManager.CONFIGURATION_PATHS_MULTI_WRITER, Configuration::asBoolean, FALSE);
1✔
513

514
        configureElement(lockManager, "lock-table", lockTable -> {
1✔
515
            final boolean lockTableDisabled = parseBoolean(getConfigAttributeValue(lockTable, "disabled"), false);
1✔
516
            final int lockTableTraceStackDepth = parseInt(getConfigAttributeValue(lockTable, "trace-stack-depth"), 0);
1✔
517

518
            setProperty(LockTable.CONFIGURATION_DISABLED, lockTableDisabled);
1✔
519
            setProperty(LockTable.CONFIGURATION_TRACE_STACK_DEPTH, lockTableTraceStackDepth);
1✔
520
        });
1✔
521

522
        configureElement(lockManager, "document", document -> {
1✔
523
            final boolean documentUsePathLocks = parseBoolean(getConfigAttributeValue(document, "use-path-locks"), false);
1✔
524

525
            setProperty(LockManager.CONFIGURATION_PATH_LOCKS_FOR_DOCUMENTS, documentUsePathLocks);
1✔
526
        });
1✔
527
    }
1✔
528

529
    private void configureRepository(final Element element) {
530
        configureProperty(element, "root", Deployment.PROPERTY_APP_ROOT, root -> {
1✔
531
            if (root.isEmpty()) {
1!
532
                return null;
×
533
            }
534
            if (root.endsWith("/")) {
1!
535
                return root;
×
536
            }
537
            return root + "/";
1✔
538
        }, null);
1✔
539
    }
1✔
540

541
    private void configureBinaryManager(final Element binaryManager) throws DatabaseConfigurationException {
542
        configureElement(binaryManager, "cache", cache -> {
1✔
543
            final String binaryCacheClass = getConfigAttributeValue(cache, "class");
1✔
544
            setProperty(BINARY_CACHE_CLASS_PROPERTY, binaryCacheClass);
1✔
545
        });
1✔
546
    }
1✔
547

548
    private void configureXQuery(final Element xquery) throws DatabaseConfigurationException {
549
        //java binding
550
        configureProperty(xquery, ENABLE_JAVA_BINDING_ATTRIBUTE, PROPERTY_ENABLE_JAVA_BINDING);
1✔
551
        configureProperty(xquery, DISABLE_DEPRECATED_FUNCTIONS_ATTRIBUTE, PROPERTY_DISABLE_DEPRECATED_FUNCTIONS, Configuration::asBoolean, DISABLE_DEPRECATED_FUNCTIONS_BY_DEFAULT);
1✔
552
        configureProperty(xquery, ENABLE_QUERY_REWRITING_ATTRIBUTE, PROPERTY_ENABLE_QUERY_REWRITING);
1✔
553
        configureProperty(xquery, ENFORCE_INDEX_USE_ATTRIBUTE, PROPERTY_ENFORCE_INDEX_USE);
1✔
554
        configureProperty(xquery, XQUERY_BACKWARD_COMPATIBLE_ATTRIBUTE, PROPERTY_XQUERY_BACKWARD_COMPATIBLE);
1✔
555
        configureProperty(xquery, XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_ATTRIBUTE, PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL, Configuration::asBoolean, XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_DEFAULT);
1✔
556
        configureProperty(xquery, PerformanceStats.CONFIG_ATTR_TRACE, PerformanceStats.CONFIG_PROPERTY_TRACE);
1✔
557

558
        // built-in-modules
559
        final Map<String, Class<?>> classMap = new HashMap<>();
1✔
560
        final Map<String, String> knownMappings = new HashMap<>();
1✔
561
        final Map<String, Map<String, List<? extends Object>>> moduleParameters = new HashMap<>();
1✔
562
        loadModuleClasses(xquery, classMap, knownMappings, moduleParameters);
1✔
563
        setProperty(PROPERTY_BUILT_IN_MODULES, classMap);
1✔
564
        setProperty(PROPERTY_STATIC_MODULE_MAP, knownMappings);
1✔
565
        setProperty(PROPERTY_MODULE_PARAMETERS, moduleParameters);
1✔
566
    }
1✔
567

568
    /**
569
     * Read list of built-in modules from the configuration. This method will only make sure
570
     * that the specified module class exists and is a subclass of {@link org.exist.xquery.Module}.
571
     *
572
     * @param xquery           configuration root
573
     * @param modulesClassMap  map containing all classes of modules
574
     * @param modulesSourceMap map containing all source uris to external resources
575
     * @throws DatabaseConfigurationException if one of the modules is configured incorrectly
576
     */
577
    private void loadModuleClasses(final Element xquery,
578
                                   final Map<String, Class<?>> modulesClassMap,
579
                                   final Map<String, String> modulesSourceMap,
580
                                   final Map<String, Map<String, List<? extends Object>>> moduleParameters
581
    ) throws DatabaseConfigurationException {
582
        // add the standard function module
583
        modulesClassMap.put(XPATH_FUNCTIONS_NS, org.exist.xquery.functions.fn.FnModule.class);
1✔
584

585
        // add other modules specified in configuration
586
        configureElement(xquery, XQUERY_BUILTIN_MODULES_CONFIGURATION_MODULES_ELEMENT_NAME, builtIn -> {
1✔
587

588
            final NodeList modules = builtIn.getElementsByTagName(XQUERY_BUILTIN_MODULES_CONFIGURATION_MODULE_ELEMENT_NAME);
1✔
589

590
            // iterate over all <module src= uri= class=> entries
591
            for (int i = 0; i < modules.getLength(); i++) {
1✔
592
                // Get element.
593
                final Element elem = (Element) modules.item(i);
1✔
594

595
                // Get attributes uri class and src
596
                final String uri = elem.getAttribute(BUILT_IN_MODULE_URI_ATTRIBUTE);
1✔
597

598
                // uri attribute is the identifier and is always required
599
                if (uri.isEmpty()) {
1!
600
                    throw new DatabaseConfigurationException("element 'module' requires an attribute 'uri'");
×
601
                }
602

603
                final String clazz = elem.getAttribute(BUILT_IN_MODULE_CLASS_ATTRIBUTE);
1✔
604
                final String source = elem.getAttribute(BUILT_IN_MODULE_SOURCE_ATTRIBUTE);
1✔
605
                // either class or source attribute must be present
606
                if (clazz.isEmpty() && source.isEmpty()) {
1!
607
                    throw new DatabaseConfigurationException("element 'module' requires either an attribute 'class' or 'src'");
×
608
                }
609

610
                if (!source.isEmpty()) {
1✔
611
                    // Store src attribute info
612

613
                    modulesSourceMap.put(uri, source);
1✔
614

615
                    if (LOG.isDebugEnabled()) {
1!
616
                        LOG.debug("Registered mapping for module '{}' to '{}'", uri, source);
×
617
                    }
618
                } else {
×
619
                    // source class attribute info
620

621
                    // Get class of module
622
                    final Class<?> moduleClass = lookupModuleClass(uri, clazz);
1✔
623

624
                    // Store class if thw module class actually exists
625
                    if (moduleClass != null) {
1✔
626
                        modulesClassMap.put(uri, moduleClass);
1✔
627
                    }
628

629
                    if (LOG.isDebugEnabled()) {
1!
630
                        LOG.debug("Configured module '{}' implemented in '{}'", uri, clazz);
×
631
                     }
632
                }
633

634
                //parse any module parameters
635
                moduleParameters.put(uri, ParametersExtractor.extract(
1✔
636
                        elem.getElementsByTagName(PARAMETER_ELEMENT_NAME)));
1✔
637
            }
638
        });
1✔
639
    }
1✔
640

641
    /**
642
     * Returns the Class object associated with the given module class name. All
643
     * important exceptions are caught. @see org.exist.xquery.Module
644
     *
645
     * @param uri   namespace of class. For logging purposes only.
646
     * @param clazz the fully qualified name of the desired module class.
647
     * @return the module Class object for the module with the specified name.
648
     * @throws DatabaseConfigurationException if the given module class is not an instance
649
     *                                        of org.exist.xquery.Module
650
     */
651
    private Class<?> lookupModuleClass(final String uri, final String clazz) throws DatabaseConfigurationException {
652
        try {
653
            final Class<?> mClass = Class.forName(clazz);
1✔
654

655
            if (!(org.exist.xquery.Module.class.isAssignableFrom(mClass))) {
1!
656
                throw (new DatabaseConfigurationException("Failed to load module: " + uri + ". " +
×
657
                        "Class " + clazz + " is not an instance of org.exist.xquery.Module."));
×
658
            }
659
            return mClass;
1✔
660
        } catch (final ClassNotFoundException e) {
1✔
661
            // Note: can't throw an exception here since this would create
662
            // problems with test cases and jar dependencies
663
            LOG.error("Configuration problem: class not found for module '{}' (ClassNotFoundException); " +
1✔
664
                    "class:'{}'; message:'{}'", uri, clazz, e.getMessage());
1✔
665

666
        } catch (final NoClassDefFoundError e) {
×
667
            LOG.error("Module {} could not be initialized due to a missing dependency (NoClassDefFoundError): " +
×
668
                    "{}", uri, e.getMessage(), e);
×
669
        }
670
        return null;
1✔
671
    }
672

673
    /**
674
     * DOCUMENT ME!
675
     *
676
     * @param xupdate configuration element
677
     * @throws NumberFormatException if one of the settings is not parseable
678
     */
679
    private void configureXUpdate(final Element xupdate) throws NumberFormatException {
680
        configureProperty(xupdate, XUPDATE_FRAGMENTATION_FACTOR_ATTRIBUTE, PROPERTY_XUPDATE_FRAGMENTATION_FACTOR, Configuration::asInteger, null);
1✔
681
        configureProperty(xupdate, XUPDATE_CONSISTENCY_CHECKS_ATTRIBUTE, PROPERTY_XUPDATE_CONSISTENCY_CHECKS, Configuration::asBoolean, FALSE);
1✔
682
    }
1✔
683

684
    private void configureSaxon(final Element saxon) {
685
        configureProperty(saxon, SaxonConfiguration.SAXON_CONFIGURATION_FILE_ATTRIBUTE, SaxonConfiguration.SAXON_CONFIGURATION_FILE_PROPERTY);
1✔
686
    }
1✔
687

688
    private void configureTransformer(final Element transformer) {
689
        final String className = getConfigAttributeValue(transformer, TRANSFORMER_CLASS_ATTRIBUTE);
1✔
690

691
        if (className != null) {
1!
692
            setProperty(PROPERTY_TRANSFORMER_CLASS, className);
1✔
693

694
            // Process any specified attributes that should be passed to the transformer factory
695

696
            final NodeList attrs = transformer.getElementsByTagName(CONFIGURATION_TRANSFORMER_ATTRIBUTE_ELEMENT_NAME);
1✔
697
            final Properties attributes = new Properties();
1✔
698

699
            for (int a = 0; a < attrs.getLength(); a++) {
1✔
700
                final Element attr = (Element) attrs.item(a);
1✔
701
                final String name = attr.getAttribute("name");
1✔
702
                final String value = attr.getAttribute("value");
1✔
703
                final String type = attr.getAttribute("type");
1✔
704

705
                if (name.isEmpty()) {
1!
706
                    LOG.warn("Discarded invalid attribute for TransformerFactory: '{}', name not specified", className);
×
707

708
                } else if (type.isEmpty() || type.equalsIgnoreCase("string")) {
1!
709
                    attributes.put(name, value);
×
710

711
                } else if (type.equalsIgnoreCase("boolean")) {
1!
712
                    attributes.put(name, Boolean.valueOf(value));
1✔
713

714
                } else if (type.equalsIgnoreCase("integer")) {
1!
715

716
                    try {
717
                        attributes.put(name, Integer.valueOf(value));
×
718
                    } catch (final NumberFormatException nfe) {
×
719
                        LOG.warn("Discarded invalid attribute for TransformerFactory: '{}', name: {}, value not integer: {}", className, name, value, nfe);
×
720
                    }
721

722
                } else {
×
723
                    // Assume string type
724
                    attributes.put(name, value);
×
725
                }
726
            }
727

728
            setProperty(PROPERTY_TRANSFORMER_ATTRIBUTES, attributes);
1✔
729
        }
730

731
        configureProperty(transformer, TRANSFORMER_CACHING_ATTRIBUTE, PROPERTY_CACHING_ATTRIBUTE, Configuration::asBoolean, FALSE);
1✔
732
    }
1✔
733

734
    private void configureParser(final Element parser) throws DatabaseConfigurationException {
735
        configureXmlParser(parser);
1✔
736
        configureHtmlToXmlParser(parser);
1✔
737
    }
1✔
738

739
    private void configureXmlParser(final Element parser) throws DatabaseConfigurationException {
740
        configureElement(parser, XML_PARSER_ELEMENT, xml ->
1✔
741
            configureElement(xml, XML_PARSER_FEATURES_ELEMENT, nlFeature -> {
1✔
742
                final Properties pFeatures = ParametersExtractor.parseFeatures(nlFeature);
1✔
743

744
                if (pFeatures.isEmpty()) {
1!
745
                    return;
1✔
746
                }
747

748
                final Map<String, Boolean> features = new HashMap<>();
×
749
                pFeatures.forEach((k, v) -> features.put(k.toString(), Boolean.valueOf(v.toString())));
×
750
                setProperty(XML_PARSER_FEATURES_PROPERTY, features);
×
751
            })
1✔
752
        );
753
    }
1✔
754

755
    private void configureHtmlToXmlParser(final Element parser) throws DatabaseConfigurationException {
756
        configureElement(parser, HTML_TO_XML_PARSER_ELEMENT, htmlToXml -> {
1✔
757
            final String htmlToXmlParserClass = getConfigAttributeValue(htmlToXml, HTML_TO_XML_PARSER_CLASS_ATTRIBUTE);
1✔
758
            setProperty(HTML_TO_XML_PARSER_PROPERTY, htmlToXmlParserClass);
1✔
759

760
            configureElement(htmlToXml, HTML_TO_XML_PARSER_PROPERTIES_ELEMENT, nlProperties -> {
1✔
761
                final Properties pProperties = ParametersExtractor.parseProperties(nlProperties);
1✔
762
                final Map<String, Object> properties = new HashMap<>();
1✔
763
                pProperties.forEach((k, v) -> properties.put(k.toString(), v));
1✔
764
                setProperty(HTML_TO_XML_PARSER_PROPERTIES_PROPERTY, properties);
1✔
765
            });
1✔
766

767
            configureElement(htmlToXml, HTML_TO_XML_PARSER_FEATURES_ELEMENT, nlFeatures -> {
1✔
768
                final Properties pFeatures = ParametersExtractor.parseFeatures(nlFeatures);
×
769
                final Map<String, Boolean> features = new HashMap<>();
×
770
                pFeatures.forEach((k, v) -> features.put(k.toString(), Boolean.valueOf(v.toString())));
×
771
                setProperty(HTML_TO_XML_PARSER_FEATURES_PROPERTY, features);
×
772
            });
×
773
        });
1✔
774
    }
1✔
775

776
    /**
777
     * DOCUMENT ME!
778
     *
779
     * @param serializer element with serializer settings
780
     */
781
    private void configureSerializer(final Element serializer) {
782
        configureProperty(serializer, OMIT_XML_DECLARATION_ATTRIBUTE, PROPERTY_OMIT_XML_DECLARATION);
1✔
783
        configureProperty(serializer, OMIT_ORIGINAL_XML_DECLARATION_ATTRIBUTE, PROPERTY_OMIT_ORIGINAL_XML_DECLARATION);
1✔
784
        configureProperty(serializer, OUTPUT_DOCTYPE_ATTRIBUTE, PROPERTY_OUTPUT_DOCTYPE);
1✔
785
        configureProperty(serializer, ENABLE_XINCLUDE_ATTRIBUTE, PROPERTY_ENABLE_XINCLUDE);
1✔
786
        configureProperty(serializer, ENABLE_XSL_ATTRIBUTE, PROPERTY_ENABLE_XSL);
1✔
787
        configureProperty(serializer, INDENT_ATTRIBUTE, PROPERTY_INDENT);
1✔
788
        configureProperty(serializer, COMPRESS_OUTPUT_ATTRIBUTE, PROPERTY_COMPRESS_OUTPUT);
1✔
789
        configureProperty(serializer, ADD_EXIST_ID_ATTRIBUTE, PROPERTY_ADD_EXIST_ID);
1✔
790
        configureProperty(serializer, TAG_MATCHING_ELEMENTS_ATTRIBUTE, PROPERTY_TAG_MATCHING_ELEMENTS);
1✔
791
        configureProperty(serializer, TAG_MATCHING_ATTRIBUTES_ATTRIBUTE, PROPERTY_TAG_MATCHING_ATTRIBUTES);
1✔
792

793
        final NodeList nlFilters = serializer.getElementsByTagName(CustomMatchListenerFactory.CONFIGURATION_ELEMENT);
1✔
794
        if (nlFilters.getLength() > 0) {
1!
795
            final List<String> filters = new ArrayList<>(nlFilters.getLength());
×
796

797
            for (int i = 0; i < nlFilters.getLength(); i++) {
×
798
                final Element filterElem = (Element) nlFilters.item(i);
×
799
                final String filterClass = filterElem.getAttribute(CustomMatchListenerFactory.CONFIGURATION_ATTR_CLASS);
×
800

801
                if (!filterClass.isEmpty()) {
×
802
                    filters.add(filterClass);
×
803
                    LOG.debug(PRP_DETAILS, CustomMatchListenerFactory.CONFIG_MATCH_LISTENERS, filterClass);
×
804
                } else {
×
805
                    LOG.warn("Configuration element " + CustomMatchListenerFactory.CONFIGURATION_ELEMENT + " needs an attribute 'class'");
×
806
                }
807
            }
808
            setProperty(CustomMatchListenerFactory.CONFIG_MATCH_LISTENERS, filters);
×
809
        }
810

811
        final NodeList backupFilters = serializer.getElementsByTagName(SystemExport.CONFIGURATION_ELEMENT);
1✔
812
        if (backupFilters.getLength() > 0) {
1!
813
            final List<String> filters = new ArrayList<>(backupFilters.getLength());
×
814

815
            for (int i = 0; i < backupFilters.getLength(); i++) {
×
816
                final Element filterElem = (Element) backupFilters.item(i);
×
817
                final String filterClass = filterElem.getAttribute(CustomMatchListenerFactory.CONFIGURATION_ATTR_CLASS);
×
818

819
                if (!filterClass.isEmpty()) {
×
820
                    filters.add(filterClass);
×
821
                    LOG.debug(PRP_DETAILS, CustomMatchListenerFactory.CONFIG_MATCH_LISTENERS, filterClass);
×
822
                } else {
×
823
                    LOG.warn("Configuration element " + SystemExport.CONFIGURATION_ELEMENT + " needs an attribute 'class'");
×
824
                }
825
                if (!filters.isEmpty()) {
×
826
                    setProperty(SystemExport.CONFIG_FILTERS, filters);
×
827
                }
828
            }
829
        }
830
    }
1✔
831

832
    /**
833
     * Reads the scheduler configuration.
834
     *
835
     * @param scheduler DOCUMENT ME!
836
     */
837
    private void configureScheduler(final Element scheduler) {
838
        final NodeList nlJobs = scheduler.getElementsByTagName(JobConfig.CONFIGURATION_JOB_ELEMENT_NAME);
1✔
839
        final List<JobConfig> jobList = new ArrayList<>();
1✔
840

841
        for (int i = 0; i < nlJobs.getLength(); i++) {
1!
842
            final Element job = (Element) nlJobs.item(i);
×
843
            if (job != null) {
×
844
                addJobToList(jobList, job);
×
845
            }
846
        }
847

848
        if (jobList.isEmpty()) {
1!
849
            return;
1✔
850
        }
851

852
        final JobConfig[] jobConfigs = new JobConfig[jobList.size()];
×
853
        for (int i = 0; i < jobList.size(); i++) {
×
854
            jobConfigs[i] = jobList.get(i);
×
855
        }
856
        setProperty(PROPERTY_SCHEDULER_JOBS, jobConfigs);
×
857
    }
×
858

859
    private void addJobToList(final List<JobConfig> jobList, final Element job) {
860
        //get the job type
861
        final String strJobType = getConfigAttributeValue(job, JOB_TYPE_ATTRIBUTE);
×
862

863
        final JobType jobType;
864
        if (strJobType == null) {
×
865
            jobType = JobType.USER; //default to user if unspecified
×
866
        } else {
×
867
            jobType = JobType.valueOf(strJobType.toUpperCase(Locale.ENGLISH));
×
868
        }
869
        //get the job resource
870
        String jobResource = getConfigAttributeValue(job, JOB_CLASS_ATTRIBUTE);
×
871
        if (jobResource == null) {
×
872
            jobResource = getConfigAttributeValue(job, JOB_XQUERY_ATTRIBUTE);
×
873
        }
874

875
        //get the job schedule
876
        String jobSchedule = getConfigAttributeValue(job, JOB_CRON_TRIGGER_ATTRIBUTE);
×
877
        if (jobSchedule == null) {
×
878
            jobSchedule = getConfigAttributeValue(job, JOB_PERIOD_ATTRIBUTE);
×
879
        }
880

881
        //create the job config
882
        try {
883
            final String jobName = getConfigAttributeValue(job, JOB_NAME_ATTRIBUTE);
×
884
            final String jobUnschedule = getConfigAttributeValue(job, JOB_UNSCHEDULE_ON_EXCEPTION);
×
885
            final JobConfig jobConfig = new JobConfig(jobType, jobName, jobResource, jobSchedule, jobUnschedule);
×
886

887
            //get and set the job delay
888
            final String jobDelay = getConfigAttributeValue(job, JOB_DELAY_ATTRIBUTE);
×
889
            if (jobDelay != null && !jobDelay.isEmpty()) {
×
890
                jobConfig.setDelay(Long.parseLong(jobDelay));
×
891
            }
892

893
            //get and set the job repeat
894
            final String jobRepeat = getConfigAttributeValue(job, JOB_REPEAT_ATTRIBUTE);
×
895
            if (jobRepeat != null && !jobRepeat.isEmpty()) {
×
896
                jobConfig.setRepeat(Integer.parseInt(jobRepeat));
×
897
            }
898

899
            final NodeList nlParam = job.getElementsByTagName(PARAMETER_ELEMENT_NAME);
×
900
            final Map<String, List<? extends Object>> params = ParametersExtractor.extract(nlParam);
×
901

902
            for (final Entry<String, List<?>> param : params.entrySet()) {
×
903
                final List<?> values = param.getValue();
×
904
                if (values != null && !values.isEmpty()) {
×
905
                    jobConfig.addParameter(param.getKey(), values.get(0).toString());
×
906

907
                    if (values.size() > 1) {
×
908
                        LOG.warn("Parameter '{}' for job '{}' has more than one value, ignoring further values.", param.getKey(), jobName);
×
909
                    }
910
                }
911
            }
912

913
            jobList.add(jobConfig);
×
914

915
            LOG.debug("Configured scheduled '{}' job '{}{}{}{}'", jobType, jobResource,
×
916
                    (jobSchedule == null) ? "" : ("' with trigger '" + jobSchedule),
×
917
                    (jobDelay == null) ? "" : ("' with delay '" + jobDelay),
×
918
                    (jobRepeat == null) ? "" : ("' repetitions '" + jobRepeat));
×
919
        } catch (final JobException je) {
×
920
            LOG.error(je);
×
921
        }
922
    }
×
923

924
    /**
925
     * DOCUMENT ME!
926
     *
927
     * @param dbHome
928
     * @param con
929
     * @throws DatabaseConfigurationException
930
     */
931
    private void configureBackend(final Optional<Path> dbHome, Element con) throws DatabaseConfigurationException {
932
        configureProperty(con, PROPERTY_DATABASE, PROPERTY_DATABASE);
1✔
933

934
        // directory for database files
935
        final String dataFiles = getConfigAttributeValue(con, DATA_DIR_ATTRIBUTE);
1✔
936

937
        if (dataFiles != null) {
1!
938
            final Path df = ConfigurationHelper.lookup(dataFiles, dbHome);
1✔
939
            if (!Files.isReadable(df)) {
1✔
940
                try {
941
                    Files.createDirectories(df);
1✔
942
                } catch (final IOException ioe) {
1✔
943
                    throw new DatabaseConfigurationException("cannot read data directory: " + df, ioe);
×
944
                }
945
            }
946
            setProperty(PROPERTY_DATA_DIR, df);
1✔
947
        }
948

949
        String cacheMem = getConfigAttributeValue(con, CACHE_SIZE_ATTRIBUTE);
1✔
950

951
        if (cacheMem != null) {
1!
952

953
            if (cacheMem.endsWith("M") || cacheMem.endsWith("m")) {
1!
954
                cacheMem = cacheMem.substring(0, cacheMem.length() - 1);
1✔
955
            }
956

957
            try {
958
                setProperty(PROPERTY_CACHE_SIZE, Integer.valueOf(cacheMem));
1✔
959
            } catch (final NumberFormatException nfe) {
1✔
960
                LOG.warn(CANNOT_CONVERT_VALUE_TO_INTEGER, PROPERTY_CACHE_SIZE, cacheMem, nfe);
×
961
            }
962
        }
963

964
        // Process the Check Max Cache value
965

966
        String checkMaxCache = getConfigAttributeValue(con, CACHE_CHECK_MAX_SIZE_ATTRIBUTE);
1✔
967

968
        if (checkMaxCache == null) {
1✔
969
            checkMaxCache = DEFAULT_CACHE_CHECK_MAX_SIZE_STRING;
1✔
970
        }
971

972
        setProperty(PROPERTY_CACHE_CHECK_MAX_SIZE, parseBoolean(checkMaxCache, true));
1✔
973

974
        String cacheShrinkThreshold = getConfigAttributeValue(con, SHRINK_THRESHOLD_ATTRIBUTE);
1✔
975

976
        if (cacheShrinkThreshold == null) {
1✔
977
            cacheShrinkThreshold = DefaultCacheManager.DEFAULT_SHRINK_THRESHOLD_STRING;
1✔
978
        }
979

980
        try {
981
            setProperty(SHRINK_THRESHOLD_PROPERTY, Integer.valueOf(cacheShrinkThreshold));
1✔
982
        } catch (final NumberFormatException nfe) {
1✔
983
            LOG.warn(CANNOT_CONVERT_VALUE_TO_INTEGER, SHRINK_THRESHOLD_PROPERTY, cacheShrinkThreshold, nfe);
×
984
        }
985

986
        String collectionCache = getConfigAttributeValue(con, CollectionCache.CACHE_SIZE_ATTRIBUTE);
1✔
987
        if (collectionCache != null) {
1✔
988
            collectionCache = collectionCache.toLowerCase();
1✔
989

990
            try {
991
                final int collectionCacheBytes;
992
                if (collectionCache.endsWith("k")) {
1!
993
                    collectionCacheBytes = 1024 * Integer.parseInt(collectionCache.substring(0, collectionCache.length() - 1));
×
994
                } else if (collectionCache.endsWith("kb")) {
1!
995
                    collectionCacheBytes = 1024 * Integer.parseInt(collectionCache.substring(0, collectionCache.length() - 2));
×
996
                } else if (collectionCache.endsWith("m")) {
1!
997
                    collectionCacheBytes = 1024 * 1024 * Integer.parseInt(collectionCache.substring(0, collectionCache.length() - 1));
1✔
998
                } else if (collectionCache.endsWith("mb")) {
1!
999
                    collectionCacheBytes = 1024 * 1024 * Integer.parseInt(collectionCache.substring(0, collectionCache.length() - 2));
×
1000
                } else if (collectionCache.endsWith("g")) {
×
1001
                    collectionCacheBytes = 1024 * 1024 * 1024 * Integer.parseInt(collectionCache.substring(0, collectionCache.length() - 1));
×
1002
                } else if (collectionCache.endsWith("gb")) {
×
1003
                    collectionCacheBytes = 1024 * 1024 * 1024 * Integer.parseInt(collectionCache.substring(0, collectionCache.length() - 2));
×
1004
                } else {
×
1005
                    collectionCacheBytes = Integer.parseInt(collectionCache);
×
1006
                }
1007

1008
                setProperty(PROPERTY_CACHE_SIZE_BYTES, collectionCacheBytes);
1✔
1009
            } catch (final NumberFormatException nfe) {
1✔
1010
                LOG.warn(CANNOT_CONVERT_VALUE_TO_INTEGER, PROPERTY_CACHE_SIZE_BYTES, collectionCache, nfe);
×
1011
            }
1012
        }
1013

1014
        configureProperty(con, NativeBroker.PAGE_SIZE_ATTRIBUTE, PROPERTY_PAGE_SIZE, Configuration::asInteger, null);
1✔
1015

1016
        //Not clear : rather looks like a buffers count
1017
        configureProperty(con, BrokerPoolConstants.COLLECTION_CACHE_SIZE_ATTRIBUTE, PROPERTY_COLLECTION_CACHE_SIZE, Configuration::asInteger, null);
1✔
1018

1019
        configureProperty(con, BrokerPoolConstants.NODES_BUFFER_ATTRIBUTE, PROPERTY_NODES_BUFFER, Configuration::asInteger, null);
1✔
1020

1021
        String diskSpace = getConfigAttributeValue(con, BrokerPoolConstants.DISK_SPACE_MIN_ATTRIBUTE);
1✔
1022
        if (diskSpace != null) {
1✔
1023
            if (diskSpace.endsWith("M") || diskSpace.endsWith("m")) {
1!
1024
                diskSpace = diskSpace.substring(0, diskSpace.length() - 1);
1✔
1025
            }
1026

1027
            try {
1028
                setProperty(DISK_SPACE_MIN_PROPERTY, Short.valueOf(diskSpace));
1✔
1029
            } catch (final NumberFormatException nfe) {
1✔
1030
                LOG.warn(CANNOT_CONVERT_VALUE_TO_INTEGER, DISK_SPACE_MIN_PROPERTY, diskSpace, nfe);
×
1031
            }
1032
        }
1033

1034
        configureProperty(con, POSIX_CHOWN_RESTRICTED_ATTRIBUTE, POSIX_CHOWN_RESTRICTED_PROPERTY, Configuration::asBoolean, TRUE);
1✔
1035

1036
        // default or configuration explicitly specifies that attributes should be preserved on copy
1037
        configureProperty(con, PRESERVE_ON_COPY_ATTRIBUTE, PRESERVE_ON_COPY_PROPERTY,
1✔
1038
                preserveOnCopyStr -> Boolean.parseBoolean(preserveOnCopyStr) ? PreserveType.PRESERVE : PreserveType.NO_PRESERVE,
1!
1039
                PreserveType.NO_PRESERVE);
1✔
1040

1041
        final NodeList startupConf = con.getElementsByTagName(BrokerPoolConstants.CONFIGURATION_STARTUP_ELEMENT_NAME);
1✔
1042
        if (startupConf.getLength() > 0) {
1✔
1043
            configureStartup((Element) startupConf.item(0));
1✔
1044
        } else {
1✔
1045
            // Prevent NPE
1046
            final List<StartupTriggerConfig> startupTriggers = new ArrayList<>();
1✔
1047
            setProperty(PROPERTY_STARTUP_TRIGGERS, startupTriggers);
1✔
1048
        }
1049

1050
        configureElement(con, BrokerPoolConstants.CONFIGURATION_POOL_ELEMENT_NAME, this::configurePool);
1✔
1051
        configureElement(con, XQueryPool.CONFIGURATION_ELEMENT_NAME, this::configureXQueryPool);
1✔
1052
        configureElement(con, XQueryWatchDog.CONFIGURATION_ELEMENT_NAME, this::configureWatchdog);
1✔
1053
        configureElement(con, BrokerPoolConstants.CONFIGURATION_RECOVERY_ELEMENT_NAME, element -> configureRecovery(dbHome, element));
1✔
1054
    }
1✔
1055

1056
    private void configureRecovery(final Optional<Path> dbHome, final Element recovery) throws DatabaseConfigurationException {
1057
        configureProperty(recovery, RECOVERY_ENABLED_ATTRIBUTE, PROPERTY_RECOVERY_ENABLED, Configuration::asBoolean, TRUE);
1✔
1058
        configureProperty(recovery, RECOVERY_SYNC_ON_COMMIT_ATTRIBUTE, PROPERTY_RECOVERY_SYNC_ON_COMMIT, Configuration::asBoolean, TRUE);
1✔
1059
        configureProperty(recovery, RECOVERY_GROUP_COMMIT_ATTRIBUTE, PROPERTY_RECOVERY_GROUP_COMMIT, Configuration::asBoolean, FALSE);
1✔
1060

1061
        final String journalDir = getConfigAttributeValue(recovery, RECOVERY_JOURNAL_DIR_ATTRIBUTE);
1✔
1062
        if (journalDir != null) {
1!
1063
            final Path rf = ConfigurationHelper.lookup(journalDir, dbHome);
1✔
1064

1065
            if (!Files.isReadable(rf)) {
1!
1066
                throw new DatabaseConfigurationException("cannot read data directory: " + rf);
×
1067
            }
1068
            setProperty(PROPERTY_RECOVERY_JOURNAL_DIR, rf);
1✔
1069
        }
1070

1071
        final String sizeLimit = getConfigAttributeValue(recovery, RECOVERY_SIZE_LIMIT_ATTRIBUTE);
1✔
1072
        if (sizeLimit != null) {
1!
1073
            try {
1074
                final int size;
1075
                if (sizeLimit.endsWith("M") || sizeLimit.endsWith("m")) {
1!
1076
                    size = Integer.parseInt(sizeLimit.substring(0, sizeLimit.length() - 1));
1✔
1077
                } else {
1✔
1078
                    size = Integer.parseInt(sizeLimit);
×
1079
                }
1080
                setProperty(PROPERTY_RECOVERY_SIZE_LIMIT, size);
1✔
1081
            } catch (final NumberFormatException e) {
1✔
1082
                throw (new DatabaseConfigurationException("size attribute in recovery section needs to be a number"));
×
1083
            }
1084
        }
1085

1086
        configureProperty(recovery, RECOVERY_FORCE_RESTART_ATTRIBUTE, PROPERTY_RECOVERY_FORCE_RESTART, Configuration::asBoolean, FALSE);
1✔
1087
        configureProperty(recovery, RECOVERY_POST_RECOVERY_CHECK, PROPERTY_RECOVERY_CHECK, Configuration::asBoolean, FALSE);
1✔
1088
    }
1✔
1089

1090
    /**
1091
     * DOCUMENT ME!
1092
     *
1093
     * @param watchDog element with watchDog settings
1094
     */
1095
    private void configureWatchdog(final Element watchDog) {
1096
        configureProperty(watchDog, "query-timeout", PROPERTY_QUERY_TIMEOUT, Configuration::asLong, null);
1✔
1097
        configureProperty(watchDog, "output-size-limit", PROPERTY_OUTPUT_SIZE_LIMIT, Configuration::asInteger, null);
1✔
1098
    }
1✔
1099

1100
    /**
1101
     * DOCUMENT ME!
1102
     *
1103
     * @param queryPool element with queryPool settings
1104
     */
1105
    private void configureXQueryPool(final Element queryPool) {
1106
        configureProperty(queryPool, MAX_STACK_SIZE_ATTRIBUTE, PROPERTY_MAX_STACK_SIZE, Configuration::asInteger, null);
1✔
1107
        configureProperty(queryPool, POOL_SIZE_ATTTRIBUTE, XQueryPool.PROPERTY_POOL_SIZE, Configuration::asInteger, null);
1✔
1108
    }
1✔
1109

1110
    private void configureStartup(final Element startup) throws DatabaseConfigurationException {
1111
        // Retrieve <triggers>
1112
        configureElement(startup, "triggers", triggers -> {
1✔
1113
            // Get <trigger>
1114
            final NodeList nlTrigger = triggers.getElementsByTagName("trigger");
1✔
1115

1116
            // If <trigger> exists and there are more than 0
1117
            if (nlTrigger.getLength() == 0) {
1!
1118
                return;
×
1119
            }
1120

1121
            // Initialize trigger configuration
1122
            var startupTriggers = (List<StartupTriggerConfig>) config
1✔
1123
                    .computeIfAbsent(PROPERTY_STARTUP_TRIGGERS, key -> new ArrayList<StartupTriggerConfig>());
1✔
1124

1125
            // Iterate over <trigger> elements
1126
            for (int i = 0; i < nlTrigger.getLength(); i++) {
1✔
1127

1128
                // Get <trigger> element
1129
                final Element trigger = (Element) nlTrigger.item(i);
1✔
1130

1131
                // Get @class
1132
                final String startupTriggerClass = trigger.getAttribute("class");
1✔
1133

1134
                try {
1135
                    // Verify if class is StartupTrigger
1136
                    final boolean isStartupTrigger = isStartupTrigger(startupTriggerClass);
1✔
1137

1138
                    // if it actually is a StartupTrigger
1139
                    if (isStartupTrigger) {
1!
1140
                        // Parse additional parameters
1141
                        final Map<String, List<? extends Object>> params = ParametersExtractor.extract(trigger.getElementsByTagName(PARAMETER_ELEMENT_NAME));
1✔
1142

1143
                        // Register trigger
1144
                        startupTriggers.add(new StartupTriggerConfig(startupTriggerClass, params));
1✔
1145

1146
                        // Done
1147
                        LOG.info("Registered StartupTrigger: {}", startupTriggerClass);
1✔
1148

1149
                    } else {
1✔
1150
                        LOG.warn("StartupTrigger: {} does not implement org.exist.storage.StartupTrigger. IGNORING!", startupTriggerClass);
×
1151
                    }
1152

1153
                } catch (final ClassNotFoundException cnfe) {
×
1154
                    LOG.error("Could not find StartupTrigger class: {}. {}", startupTriggerClass, cnfe.getMessage(), cnfe);
×
1155
                }
1156
            }
1157
        });
1✔
1158
    }
1✔
1159

1160
    private static boolean isStartupTrigger(String startupTriggerClass) throws ClassNotFoundException {
1161
        for (final Class<?> iface : Class.forName(startupTriggerClass).getInterfaces()) {
1!
1162
            if (ORG_EXIST_STORAGE_STARTUP_TRIGGER.equals(iface.getName())) {
1!
1163
                return true;
1✔
1164
            }
1165
        }
1166
        return false;
×
1167
    }
1168

1169
    private void configurePool(final Element pool) {
1170
        configureProperty(pool, MIN_CONNECTIONS_ATTRIBUTE, PROPERTY_MIN_CONNECTIONS, Configuration::asInteger, null);
1✔
1171
        configureProperty(pool, MAX_CONNECTIONS_ATTRIBUTE, PROPERTY_MAX_CONNECTIONS, Configuration::asInteger, null);
1✔
1172
        configureProperty(pool, SYNC_PERIOD_ATTRIBUTE, PROPERTY_SYNC_PERIOD, Configuration::asLong, null);
1✔
1173
        configureProperty(pool, SHUTDOWN_DELAY_ATTRIBUTE, PROPERTY_SHUTDOWN_DELAY, Configuration::asLong, null);
1✔
1174
    }
1✔
1175

1176
    private void configureIndexer(final Document doc, final Element indexer) throws DatabaseConfigurationException {
1177
        configureProperty(indexer, INDEX_CASE_SENSITIVE_ATTRIBUTE, PROPERTY_INDEX_CASE_SENSITIVE, Configuration::asBoolean, FALSE);
1✔
1178

1179
        final String indexDepth = getConfigAttributeValue(indexer, INDEX_DEPTH_ATTRIBUTE);
1✔
1180

1181
        if (indexDepth != null) {
1!
1182
            try {
1183
                int depth = Integer.parseInt(indexDepth);
1✔
1184

1185
                if (depth < 3) {
1!
1186
                    LOG.warn("parameter index-depth should be >= 3 or you will experience a severe "
×
1187
                            + "performance loss for node updates (XUpdate or XQuery update extensions)");
1188
                    depth = 3;
×
1189
                }
1190
                setProperty(PROPERTY_INDEX_DEPTH, depth);
1✔
1191
            } catch (final NumberFormatException e) {
1✔
1192
                LOG.warn(e);
×
1193
            }
1194
        }
1195

1196
        configureProperty(indexer, SUPPRESS_WHITESPACE_ATTRIBUTE, PROPERTY_SUPPRESS_WHITESPACE);
1✔
1197
        configureProperty(indexer, PRESERVE_WS_MIXED_CONTENT_ATTRIBUTE, PROPERTY_PRESERVE_WS_MIXED_CONTENT, Configuration::asBoolean, FALSE);
1✔
1198

1199
        // index settings
1200
        final NodeList cl = doc.getElementsByTagName(CONFIGURATION_INDEX_ELEMENT_NAME);
1✔
1201

1202
        if (cl.getLength() > 0) {
1!
1203
            final Element elem = (Element) cl.item(0);
1✔
1204
            final IndexSpec spec = new IndexSpec(null, elem);
1✔
1205
            setProperty(PROPERTY_INDEXER_CONFIG, spec);
1✔
1206
        }
1207

1208
        // index modules
1209
        final NodeList modules = indexer.getElementsByTagName(IndexManager.CONFIGURATION_ELEMENT_NAME);
1✔
1210

1211
        if (modules.getLength() == 0) {
1!
1212
            return;
×
1213
        }
1214
        final NodeList module = ((Element) modules.item(0)).getElementsByTagName(IndexManager.CONFIGURATION_MODULE_ELEMENT_NAME);
1✔
1215
        final IndexModuleConfig[] modConfig = new IndexModuleConfig[module.getLength()];
1✔
1216

1217
        for (int i = 0; i < module.getLength(); i++) {
1✔
1218
            final Element elem = (Element) module.item(i);
1✔
1219
            final String className = elem.getAttribute(IndexManager.INDEXER_MODULES_CLASS_ATTRIBUTE);
1✔
1220
            final String id = elem.getAttribute(IndexManager.INDEXER_MODULES_ID_ATTRIBUTE);
1✔
1221

1222
            if (className.isEmpty()) {
1!
1223
                throw new DatabaseConfigurationException("Required attribute class is missing for module");
×
1224
            }
1225

1226
            if (id.isEmpty()) {
1!
1227
                throw new DatabaseConfigurationException("Required attribute id is missing for module");
×
1228
            }
1229
            modConfig[i] = new IndexModuleConfig(id, className, elem);
1✔
1230
        }
1231
        setProperty(IndexManager.PROPERTY_INDEXER_MODULES, modConfig);
1✔
1232
    }
1✔
1233

1234
    private void configureValidation(final Optional<Path> dbHome, final Element validation) {
1235
        // Determine validation mode
1236
        configureProperty(validation, XMLReaderObjectFactory.VALIDATION_MODE_ATTRIBUTE, PROPERTY_VALIDATION_MODE);
1✔
1237

1238
        // cache
1239
        setProperty(XMLReaderObjectFactory.GRAMMAR_POOL, new GrammarPool());
1✔
1240

1241
        // Configure the Entity Resolver
1242
        final NodeList entityResolver = validation.getElementsByTagName(XMLReaderObjectFactory.CONFIGURATION_ENTITY_RESOLVER_ELEMENT_NAME);
1✔
1243
        if (entityResolver.getLength() == 0) {
1!
1244
            return;
×
1245
        }
1246
        LOG.info("Creating xmlresolver.org OASIS Catalog resolver");
1✔
1247

1248
        final Element elemEntityResolver = (Element) entityResolver.item(0);
1✔
1249
        final NodeList nlCatalogs = elemEntityResolver.getElementsByTagName(XMLReaderObjectFactory.CONFIGURATION_CATALOG_ELEMENT_NAME);
1✔
1250

1251
        // Determine webapps directory. SingleInstanceConfiguration cannot
1252
        // be used at this phase. Trick is to check whether dbHOME is
1253
        // pointing to a WEB-INF directory, meaning inside the war file.
1254
        final Path webappHome = dbHome.map(h -> {
1✔
1255
            if (FileUtils.fileName(h).endsWith("WEB-INF")) {
1!
1256
                return h.getParent().toAbsolutePath();
×
1257
            }
1258
            return h.resolve("webapp").toAbsolutePath();
1✔
1259
        }).orElse(Paths.get("webapp").toAbsolutePath());
1✔
1260

1261
        if (LOG.isDebugEnabled()) {
1!
1262
            LOG.debug("Found {} catalog uri entries.", nlCatalogs.getLength());
×
1263
            LOG.debug("Using dbHome={}", dbHome);
×
1264
            LOG.debug("using webappHome={}", webappHome);
×
1265
        }
1266

1267
        // Get the Catalog URIs
1268
        final List<String> catalogUris = new ArrayList<>();
1✔
1269
        for (int i = 0; i < nlCatalogs.getLength(); i++) {
1✔
1270
            final String uriAttributeValue = ((Element) nlCatalogs.item(i)).getAttribute("uri");
1✔
1271

1272
            if (!uriAttributeValue.isEmpty()) {
1!
1273
                final String uri;
1274
                // Substitute string, creating an uri from a local file
1275
                if (uriAttributeValue.contains("${WEBAPP_HOME}")) {
1✔
1276
                    uri = uriAttributeValue.replace("${WEBAPP_HOME}", webappHome.toUri().toString());
1✔
1277
                } else if (uriAttributeValue.contains("${EXIST_HOME}")) {
1!
1278
                    uri = uriAttributeValue.replace("${EXIST_HOME}", dbHome.toString());
×
1279
                } else {
×
1280
                    uri = uriAttributeValue;
1✔
1281
                }
1282

1283
                // Add uri to configuration
1284
                LOG.info("Adding Catalog URI: {}", uri);
1✔
1285
                catalogUris.add(uri);
1✔
1286
            }
1287
        }
1288

1289
        // Store all configured URIs
1290
        setProperty(XMLReaderObjectFactory.CATALOG_URIS, catalogUris);
1✔
1291

1292
        // Create and Store the resolver
1293
        try {
1294
            final List<Tuple2<String, Optional<InputSource>>> catalogs = catalogUris.stream()
1✔
1295
                    .map(catalogUri -> Tuple(catalogUri, Optional.<InputSource>empty()))
1✔
1296
                    .toList();
1✔
1297
            final Resolver resolver = ResolverFactory.newResolver(catalogs);
1✔
1298
            setProperty(XMLReaderObjectFactory.CATALOG_RESOLVER, resolver);
1✔
1299
        } catch (final URISyntaxException e) {
1✔
1300
            LOG.error("Unable to parse catalog uri: {}", e.getMessage(), e);
×
1301
        }
1302
    }
1✔
1303

1304
    private void configureRpcServer(final Element validation) throws DatabaseConfigurationException {
1305
        configureElement(validation, "content-file", element ->
1✔
1306
            configureProperty(element, "in-memory-size", PROPERTY_IN_MEMORY_SIZE, Configuration::asInteger, DEFAULT_IN_MEMORY_SIZE)
1✔
1307
        );
1308
        configureElement(validation, "content-file-pool", element -> {
1✔
1309
            configureProperty(element, "size", ContentFilePool.PROPERTY_POOL_SIZE, Configuration::asInteger, -1);
1✔
1310
            configureProperty(element, "max-idle", ContentFilePool.PROPERTY_POOL_MAX_IDLE, Configuration::asInteger, 5);
1✔
1311
        });
1✔
1312
    }
1✔
1313

1314
    /**
1315
     * Gets the value of a configuration attribute
1316
     * <p>
1317
     * The value typically is specified in the conf.xml file, but can be overridden with using a System Property
1318
     *
1319
     * @param element       The attribute's parent element
1320
     * @param attributeName The name of the attribute
1321
     * @return The value of the attribute, or null if the attribute does not exist or has an empty value
1322
     */
1323
    private @Nullable String getConfigAttributeValue(final Element element, final String attributeName) {
1324
        if (element == null || attributeName == null) {
1!
1325
            return null;
×
1326
        }
1327

1328
        final String property = getAttributeSystemPropertyName(element, attributeName);
1✔
1329
        final String value = System.getProperty(property);
1✔
1330

1331
        if (value != null) {
1✔
1332
            LOG.warn("Configuration value overridden by system property: {}, with value: {}", property, value);
1✔
1333
            return value;
1✔
1334
        }
1335
        // If the value has not been overridden in a system property, then get it from the configuration
1336
        return nullIfEmpty(element.getAttribute(attributeName));
1✔
1337
    }
1338

1339
    private <T> void configureProperty(final Element element, final String attributeName, final String propertyName,
1340
                                       final Function<String, T> valueConverter, @Nullable final T defaultValue) {
1341
        @Nullable final String attributeValue = getConfigAttributeValue(element, attributeName);
1✔
1342
        if (attributeValue != null) {
1✔
1343
            T value = valueConverter.apply(attributeValue);
1✔
1344
            if (value != null) {
1!
1345
                setProperty(propertyName, value);
1✔
1346
                return;
1✔
1347
            }
1348
        }
1349
        if (defaultValue != null) {
1✔
1350
            setProperty(propertyName, defaultValue);
1✔
1351
        }
1352
    }
1✔
1353

1354
    private void configureProperty(final Element element, final String attributeName, final String propertyName) {
1355
        configureProperty(element, attributeName, propertyName, Function.identity(), null);
1✔
1356
    }
1✔
1357

1358
    /**
1359
     * Generates a suitable system property name from the given config attribute and parent element.
1360
     * <p>
1361
     * values are of the form org.element.element.....attribute and follow the hierarchical structure of the conf.xml file.
1362
     * For example, the db-connection cacheSize property name would be org.exist.db-connection.cacheSize
1363
     *
1364
     * @param element       The attribute's parent element
1365
     * @param attributeName The name of the attribute
1366
     * @return The generated system property name
1367
     */
1368
    private String getAttributeSystemPropertyName(Element element, String attributeName) {
1369
        final StringBuilder property = new StringBuilder(attributeName);
1✔
1370

1371
        property.insert(0, ".");
1✔
1372
        property.insert(0, element.getLocalName());
1✔
1373

1374
        Node parent = element.getParentNode();
1✔
1375
        while (parent instanceof Element) {
1✔
1376
            final String parentName = parent.getLocalName();
1✔
1377

1378
            property.insert(0, ".");
1✔
1379
            property.insert(0, parentName);
1✔
1380

1381
            parent = parent.getParentNode();
1✔
1382
        }
1383

1384
        property.insert(0, "org.");
1✔
1385

1386
        return property.toString();
1✔
1387
    }
1388

1389
    public Optional<Path> getConfigFilePath() {
1390
        return configFilePath;
1✔
1391
    }
1392

1393
    public Optional<Path> getExistHome() {
1394
        return existHome;
1✔
1395
    }
1396

1397
    public Object getProperty(final String name) {
1398
        return config.get(name);
1✔
1399
    }
1400

1401
    public <T> T getProperty(final String name, final T defaultValue) {
1402
        return Optional.ofNullable((T) config.get(name)).orElse(defaultValue);
1✔
1403
    }
1404

1405
    public boolean hasProperty(final String name) {
1406
        return config.containsKey(name);
×
1407
    }
1408

1409
    public void setProperty(final String name, final Object obj) {
1410
        config.put(name, obj);
1✔
1411
        LOG.debug(PRP_DETAILS, name, obj);
1✔
1412
    }
1✔
1413

1414
    public void removeProperty(final String name) {
1415
        config.remove(name);
×
1416
    }
×
1417

1418
    public int getInteger(final String name) {
1419
        return getInteger(name, -1);
1✔
1420
    }
1421

1422
    public int getInteger(final String name, final int defaultValue) {
1423
        return Optional.ofNullable(getProperty(name))
1✔
1424
                .filter(Integer.class::isInstance)
1✔
1425
                .map(v -> (int) v)
1✔
1426
                .orElse(defaultValue);
1✔
1427
    }
1428

1429
    /**
1430
     * (non-Javadoc).
1431
     *
1432
     * @param exception DOCUMENT ME!
1433
     * @throws SAXException DOCUMENT ME!
1434
     * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
1435
     */
1436
    @Override
1437
    public void error(SAXParseException exception) throws SAXException {
1438
        LOG.error(ERROR_READING_CONFIGURATION_FILE_LINE, exception.getLineNumber(), exception.getMessage(), exception);
×
1439
    }
×
1440

1441
    /**
1442
     * (non-Javadoc).
1443
     *
1444
     * @param exception DOCUMENT ME!
1445
     * @throws SAXException DOCUMENT ME!
1446
     * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
1447
     */
1448
    @Override
1449
    public void fatalError(SAXParseException exception) throws SAXException {
1450
        LOG.error(ERROR_READING_CONFIGURATION_FILE_LINE, exception.getLineNumber(), exception.getMessage(), exception);
×
1451
    }
×
1452

1453
    /**
1454
     * (non-Javadoc).
1455
     *
1456
     * @param exception DOCUMENT ME!
1457
     * @throws SAXException DOCUMENT ME!
1458
     * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
1459
     */
1460
    @Override
1461
    public void warning(SAXParseException exception) throws SAXException {
1462
        LOG.error(ERROR_READING_CONFIGURATION_FILE_LINE, exception.getLineNumber(), exception.getMessage(), exception);
×
1463
    }
×
1464

1465
    public record StartupTriggerConfig(String clazz, Map<String, List<? extends Object>> params) {
1✔
1466
    }
1467

1468
    public record IndexModuleConfig(String id, String className, Element config) {
1✔
1469
    }
1470

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