• 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

62.94
/exist-core/src/main/java/org/exist/collections/CollectionConfiguration.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;
50

51
import java.util.ArrayList;
52
import java.util.List;
53
import java.util.Map;
54

55
import org.apache.logging.log4j.LogManager;
56
import org.apache.logging.log4j.Logger;
57
import org.exist.collections.triggers.CollectionTrigger;
58
import org.exist.collections.triggers.CollectionTriggerProxy;
59
import org.exist.collections.triggers.DocumentTrigger;
60
import org.exist.collections.triggers.DocumentTriggerProxy;
61
import org.exist.collections.triggers.Trigger;
62
import org.exist.collections.triggers.TriggerException;
63
import org.exist.collections.triggers.TriggerProxy;
64
import org.exist.config.annotation.ConfigurationClass;
65
import org.exist.storage.BrokerPool;
66
import org.exist.storage.DBBroker;
67
import org.exist.storage.IndexSpec;
68
import org.exist.util.DatabaseConfigurationException;
69
import org.exist.util.ParametersExtractor;
70
import org.exist.util.XMLReaderObjectFactory;
71
import org.exist.xmldb.XmldbURI;
72
import org.w3c.dom.Document;
73
import org.w3c.dom.Element;
74
import org.w3c.dom.Node;
75
import org.w3c.dom.NodeList;
76

77
@ConfigurationClass("collection")
78
public class CollectionConfiguration {
79

80
    public final static String COLLECTION_CONFIG_SUFFIX = ".xconf";
81
    public final static XmldbURI COLLECTION_CONFIG_SUFFIX_URI = XmldbURI.create(COLLECTION_CONFIG_SUFFIX);
1✔
82
    public final static String DEFAULT_COLLECTION_CONFIG_FILE = "collection" + COLLECTION_CONFIG_SUFFIX;
83
    public final static XmldbURI DEFAULT_COLLECTION_CONFIG_FILE_URI = XmldbURI.create(DEFAULT_COLLECTION_CONFIG_FILE);
1✔
84

85
    public final static String NAMESPACE = "http://exist-db.org/collection-config/1.0";
86

87
    private static final String ROOT_ELEMENT = "collection";
88

89
    /**
90
     * First level element in a collection configuration document
91
     */
92
    private static final String TRIGGERS_ELEMENT = "triggers";
93
    private static final String TRIGGER_ELEMENT = "trigger";
94
    private static final String CLASS_ATTRIBUTE = "class";
95
    private static final String PARAMETER_ELEMENT = "parameter";
96

97
    /**
98
     * First level element in a collection configuration document
99
     */
100
    private static final String INDEX_ELEMENT = "index";
101
    private static final String GROUP_ELEMENT = "default-group";
102
    private static final String RESOURCE_ATTR = "resource";
103
    private static final String COLLECTION_ATTR = "collection";
104

105
    private static final String VALIDATION_ELEMENT = "validation";
106
    private static final String VALIDATION_MODE_ATTR = "mode";
107

108
    private static final Logger LOG = LogManager.getLogger(CollectionConfiguration.class);
1✔
109

110
    private final List<TriggerProxy<? extends CollectionTrigger>> colTriggers = new ArrayList<>();
1✔
111
    private final List<TriggerProxy<? extends DocumentTrigger>> docTriggers = new ArrayList<>();
1✔
112

113
    private IndexSpec indexSpec = null;
1✔
114

115
    private XmldbURI docName = null;
1✔
116
    private XmldbURI srcCollectionURI;
117

118
    private XMLReaderObjectFactory.VALIDATION_SETTING validationMode = XMLReaderObjectFactory.VALIDATION_SETTING.UNKNOWN;
1✔
119

120
    private final BrokerPool pool;
121

122
    public CollectionConfiguration(final BrokerPool pool) {
1✔
123
        this.pool = pool;
1✔
124
    }
1✔
125

126
    /**
127
     * @param broker the database broker
128
     * @param doc collection configuration document
129
     * @param checkOnly true to only check
130
     * @param srcCollectionURI The collection from which the document is being read.  This
131
     *                         is not necessarily the same as this.collection.getURI() because the
132
     *                         source document may have come from a parent collection.
133
     * @param docName The name of the document being read
134

135
     * @throws CollectionConfigurationException if an error occurs whilst reading the collection configuration
136
     */
137
    protected void read(final DBBroker broker, final Document doc, final boolean checkOnly,
138
            final XmldbURI srcCollectionURI, final XmldbURI docName) throws CollectionConfigurationException {
139
        if (!checkOnly) {
1!
140
            this.docName = docName;
1✔
141
            this.srcCollectionURI = srcCollectionURI;
1✔
142
        }
143
        final Element root = doc.getDocumentElement();
1✔
144
        if (root == null) {
1!
145
            throwOrLog("Configuration document can not be parsed", checkOnly);
×
146
            return;
×
147
        }
148
        if (!ROOT_ELEMENT.equals(root.getLocalName())) {
1✔
149
            throwOrLog("Expected element '" + ROOT_ELEMENT +
1✔
150
                    "' in configuration document. Got element '" + root.getLocalName() + "'", checkOnly);
1✔
151
            return;
1✔
152
        }
153
        if (root.getNamespaceURI() == null || !NAMESPACE.equals(root.getNamespaceURI())) {
1!
154
            throwOrLog("Expected namespace '" + NAMESPACE +
×
155
                    "' for element '" + PARAMETER_ELEMENT +
156
                    "' in configuration document. Got '" + root.getNamespaceURI() + "'", checkOnly);
×
157
            return;
×
158
        }
159
        final NodeList childNodes = root.getChildNodes();
1✔
160
        for (int i = 0; i < childNodes.getLength(); i++) {
1✔
161
            Node node = childNodes.item(i);
1✔
162
            if (NAMESPACE.equals(node.getNamespaceURI())) {
1✔
163
                if (TRIGGERS_ELEMENT.equals(node.getLocalName())) {
1✔
164
                    final NodeList triggers = node.getChildNodes();
1✔
165
                    for (int j = 0; j < triggers.getLength(); j++) {
1✔
166
                        node = triggers.item(j);
1✔
167
                        if (node.getNodeType() == Node.ELEMENT_NODE && node.getLocalName().equals(TRIGGER_ELEMENT)) {
1!
168
                            configureTrigger(broker.getBrokerPool().getClassLoader(), (Element) node, srcCollectionURI, checkOnly);
1✔
169
                        }
170
                    }
171
                } else if (INDEX_ELEMENT.equals(node.getLocalName())) {
1✔
172
                    final Element elem = (Element) node;
1✔
173
                    try {
174
                        if (indexSpec == null) {
1!
175
                            indexSpec = new IndexSpec(broker, elem);
1✔
176
                        } else {
1✔
177
                            indexSpec.read(broker, elem);
×
178
                        }
179
                    } catch (final DatabaseConfigurationException e) {
1✔
180
                        if (checkOnly) {
1!
181
                            throw new CollectionConfigurationException(e.getMessage(), e);
×
182
                        } else {
183
                            LOG.warn(e.getMessage(), e);
1✔
184
                        }
185
                    }
186

187
                } else if (VALIDATION_ELEMENT.equals(node.getLocalName())) {
1!
188
                    final Element elem = (Element) node;
1✔
189
                    final String mode = elem.getAttribute(VALIDATION_MODE_ATTR);
1✔
190
                    if (mode.isEmpty()) {
1!
191
                        LOG.debug("Unable to determine validation mode in {}", srcCollectionURI);
×
192
                        validationMode = XMLReaderObjectFactory.VALIDATION_SETTING.UNKNOWN;
×
193
                    } else {
×
194
                        LOG.debug("{} : Validation mode={}", srcCollectionURI, mode);
1✔
195
                        validationMode = XMLReaderObjectFactory.VALIDATION_SETTING.fromOption(mode);
1✔
196
                    }
197

198
                } else {
1✔
199
                    throwOrLog("Ignored node '" + node.getLocalName() +
×
200
                            "' in configuration document", checkOnly);
×
201
                    //TODO : throw an exception like above ? -pb
202
                }
203

204
            } else if (node.getNodeType() == Node.ELEMENT_NODE) {
1!
205
                throwOrLog("Ignored node '" + node.getLocalName() + "' in namespace '" +
×
206
                        node.getNamespaceURI() + "' in configuration document", checkOnly);
×
207
            }
208
        }
209
    }
1✔
210

211
    private void throwOrLog(final String message, final boolean throwExceptions) throws CollectionConfigurationException {
212
        if (throwExceptions) {
1!
213
            throw new CollectionConfigurationException(message);
×
214
        } else {
215
            LOG.warn(message);
1✔
216
        }
217
    }
1✔
218

219
    public XmldbURI getDocName() {
220
        return docName;
1✔
221
    }
222

223
    protected void setIndexConfiguration(final IndexSpec spec) {
224
        this.indexSpec = spec;
1✔
225
    }
1✔
226

227
    public XmldbURI getSourceCollectionURI() {
228
        return srcCollectionURI;
×
229
    }
230

231
    public XMLReaderObjectFactory.VALIDATION_SETTING getValidationMode() {
232
        return validationMode;
1✔
233
    }
234

235
    public IndexSpec getIndexConfiguration() {
236
        return indexSpec;
1✔
237
    }
238

239
    private void configureTrigger(final ClassLoader cl, final Element triggerElement, final XmldbURI collectionConfigurationURI, final boolean testOnly) throws CollectionConfigurationException {
240

241
        //TODO : rely on schema-driven validation -pb
242

243
        final String classname = triggerElement.getAttributes().getNamedItem(CLASS_ATTRIBUTE).getNodeValue();
1✔
244

245
        try {
246
            final Class clazz = Class.forName(classname, true, cl);
1✔
247
            if (!Trigger.class.isAssignableFrom(clazz)) {
1!
248
                throwOrLog("Trigger's class '" + classname + "' is not assignable from '" + Trigger.class + "'", testOnly);
×
249
                return;
×
250
            }
251
            final NodeList nlParameter = triggerElement.getElementsByTagNameNS(NAMESPACE, PARAMETER_ELEMENT);
1✔
252
            final Map<String, List<? extends Object>> parameters = ParametersExtractor.extract(nlParameter);
1✔
253

254
            boolean added = false;
1✔
255
            if (DocumentTrigger.class.isAssignableFrom(clazz)) {
1✔
256
                docTriggers.add(new DocumentTriggerProxy((Class<? extends DocumentTrigger>) clazz, parameters));
1✔
257
                added = true;
1✔
258
            }
259

260
            if (CollectionTrigger.class.isAssignableFrom(clazz)) {
1✔
261
                colTriggers.add(new CollectionTriggerProxy((Class<? extends CollectionTrigger>) clazz, parameters));
1✔
262
                added = true;
1✔
263
            }
264

265
            if (!added) {
1!
266
                throw new TriggerException("Unknown Trigger class type: " + clazz.getName());
×
267
            }
268

269
        } catch (final ClassNotFoundException | TriggerException e) {
1✔
270
            if (testOnly) {
1!
271
                throw new CollectionConfigurationException(e.getMessage(), e);
×
272
            } else {
273
                LOG.warn("Trigger class not found: {}", e.getMessage(), e);
1✔
274
            }
275
        }
276
    }
1✔
277

278
    public List<TriggerProxy<? extends CollectionTrigger>> collectionTriggers() {
279
        return colTriggers;
1✔
280
    }
281

282
    public List<TriggerProxy<? extends DocumentTrigger>> documentTriggers() {
283
        return docTriggers;
1✔
284
    }
285

286
    public boolean triggerRegistered(final Class<?> triggerClass) {
287
        if(DocumentTrigger.class.isAssignableFrom(triggerClass)) {
×
288
            if(hasTriggerProxy(docTriggers, (Class<? extends DocumentTrigger>)triggerClass)) {
×
289
                return true;
×
290
            }
291
        }
292

293
        if(CollectionTrigger.class.isAssignableFrom(triggerClass)) {
×
294
            if(hasTriggerProxy(colTriggers, (Class<? extends CollectionTrigger>)triggerClass)) {
×
295
                return true;
×
296
            }
297
        }
298

299
        return false;
×
300
    }
301

302
    private <T> boolean hasTriggerProxy(final List<TriggerProxy<? extends T>> triggerProxies, final Class<? extends T> triggerProxyClazz) {
303
        for(final TriggerProxy<? extends T> triggerProxy : triggerProxies) {
×
304
            if(triggerProxy.getClazz() == triggerProxyClazz) {
×
305
                return true;
×
306
            }
307
        }
308
        return false;
×
309
    }
310

311
    @Override
312
    public String toString() {
313
        final StringBuilder result = new StringBuilder();
×
314
        if (indexSpec != null) {
×
315
            result.append(indexSpec.toString()).append(System.getProperty("line.separator"));
×
316
        }
317
        return result.toString();
×
318
    }
319
}
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