• 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

82.4
/exist-core/src/main/java/org/exist/storage/IndexSpec.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.storage;
50

51
import org.exist.Namespaces;
52
import org.exist.collections.CollectionConfiguration;
53
import org.exist.dom.QName;
54
import org.exist.dom.TypedQNameComparator;
55
import org.exist.util.DatabaseConfigurationException;
56
import org.w3c.dom.Attr;
57
import org.w3c.dom.Element;
58
import org.w3c.dom.NamedNodeMap;
59
import org.w3c.dom.Node;
60
import org.w3c.dom.NodeList;
61

62
import java.util.*;
63

64
/**
65
 * Top class for index definitions as specified in a collection configuration
66
 * or the main configuration file. The IndexSpec for a given collection can be retrieved through method
67
 * {@link org.exist.collections.Collection#getIndexConfiguration(DBBroker)}.
68
 *  
69
 *  An index definition should have the following structure:
70
 *  
71
 *  <pre>
72
 *  &lt;index index-depth="idx-depth"&gt;
73
 *      &lt;create path="node-path" type="schema-type"&gt;
74
 *  &lt;/index&gt;
75
 *  </pre>
76
 *  
77
 * @author wolf
78
 */
79
@SuppressWarnings("ForLoopReplaceableByForEach")
80
public class IndexSpec {
81

82
    private static final String TYPE_ATTRIB = "type";
83
    private static final String PATH_ATTRIB = "path";
84
    private static final String CREATE_ELEMENT = "create";
85
    private static final String QNAME_ATTRIB = "qname";
86

87
    private GeneralRangeIndexSpec specs[] = null;
1✔
88
    private Map<QName, QNameRangeIndexSpec> qnameSpecs = new TreeMap<>(new TypedQNameComparator());
1✔
89

90
    private Map<String, Object> customIndexSpecs = null;
1✔
91

92
    public IndexSpec(DBBroker broker, Element index) throws DatabaseConfigurationException {
1✔
93
        read(broker, index);
1✔
94
    }
1✔
95

96
    /**
97
     * Read index configurations from an "index" element node.
98
     * The node should have zero or more "create" nodes.
99
     * The "create" elements add a {@link GeneralRangeIndexSpec} to the current configuration.
100
     *  
101
     * @param index index configuration
102
     * @param broker the DBBroker
103
     * @throws DatabaseConfigurationException in response to a configuration error
104
     *
105
     */
106
    public void read(DBBroker broker, Element index) throws DatabaseConfigurationException {
107
        final Map<String, String> namespaces = getNamespaceMap(index);
1✔
108
        final NodeList childNodes = index.getChildNodes();
1✔
109
        for (int i = 0; i < childNodes.getLength(); i++) {
1✔
110
            final Node node = childNodes.item(i);
1✔
111
            if (node.getNodeType() == Node.ELEMENT_NODE) {
1✔
112
                if (CREATE_ELEMENT.equals(node.getLocalName())) {
1✔
113
                    final Element elem = (Element) node;
1✔
114
                    final String type = elem.getAttribute(TYPE_ATTRIB);
1✔
115
                    if (elem.hasAttribute(QNAME_ATTRIB)) {
1✔
116
                        final String qname = elem.getAttribute(QNAME_ATTRIB);
1✔
117
                        final QNameRangeIndexSpec qnIdx = new QNameRangeIndexSpec(namespaces, qname, type);
1✔
118
                        qnameSpecs.put(qnIdx.getQName(), qnIdx);
1✔
119
                    } else if (elem.hasAttribute(PATH_ATTRIB)) {
1✔
120
                        final String path = elem.getAttribute(PATH_ATTRIB);
1✔
121
                        final GeneralRangeIndexSpec valueIdx = new GeneralRangeIndexSpec(namespaces, path, type);
1✔
122
                        addValueIndex(valueIdx);
1✔
123
                    } else {
1✔
124
                        final String error_message = "Configuration error: element " + elem.getNodeName() +
1✔
125
                            " must have attribute " + PATH_ATTRIB + " or " + QNAME_ATTRIB;
126
                        throw new DatabaseConfigurationException(error_message);
1✔
127
                    }
128
                }
129
            }
130
        }
131
        // configure custom indexes, but not if broker is null (which means we are reading
132
        // the default index config from conf.xml)
133
        if (broker != null)
1✔
134
            {customIndexSpecs = broker.getIndexController().configure(childNodes, namespaces);}
1✔
135
    }
1✔
136

137
    /**
138
     * Returns the configuration object registered for the non-core
139
     * index identified by id.
140
     *
141
     * @param id the id used to identify this index.
142
     * @return the configuration object registered for the index or null.
143
     */
144
    public Object getCustomIndexSpec(String id) {
145
        return customIndexSpecs == null ? null : customIndexSpecs.get(id);
×
146
    }
147

148
    /**
149
     * @return the {@link GeneralRangeIndexSpec} defined for the given
150
     * node path or null if no index has been configured.
151
     * 
152
     * @param path given node path
153
     */
154
    public GeneralRangeIndexSpec getIndexByPath(NodePath path) {
155
        if(specs != null) {
1✔
156
            for (GeneralRangeIndexSpec spec : specs) {
1✔
157
                if (spec.matches(path)) {
1✔
158
                    return spec;
1✔
159
                }
160
            }
161
        }
162
        return null;
1✔
163
    }
164

165
    public QNameRangeIndexSpec getIndexByQName(QName name) {
166
        return qnameSpecs.get(name);
1✔
167
    }
168

169
    public boolean hasIndexesByPath() {
170
        return specs != null && specs.length > 0;
1!
171
    }
172

173
    public boolean hasIndexesByQName() {
174
        return qnameSpecs.size() > 0;
1✔
175
    }
176

177
    public List<QName> getIndexedQNames() {
178
        return new ArrayList<>(qnameSpecs.keySet());
1✔
179
    }
180

181
    /**
182
     * Add a {@link GeneralRangeIndexSpec}.
183
     * 
184
     * @param valueIdx
185
     */
186
    private void addValueIndex(GeneralRangeIndexSpec valueIdx) {
187
        if(specs == null) {
1✔
188
            specs = new GeneralRangeIndexSpec[1];
1✔
189
            specs[0] = valueIdx;
1✔
190
        } else {
1✔
191
            GeneralRangeIndexSpec nspecs[] = new GeneralRangeIndexSpec[specs.length + 1];
1✔
192
            System.arraycopy(specs, 0, nspecs, 0, specs.length);
1✔
193
            nspecs[specs.length] = valueIdx;
1✔
194
            specs = nspecs;
1✔
195
        }
196
    }
1✔
197

198
    /**
199
     * Returns a map containing all prefix/namespace mappings declared in
200
     * the index element.
201
     * 
202
     * @param elem
203
     * @return The namespaces map.
204
     */
205
    private Map<String, String> getNamespaceMap(Element elem) {
206
        final Node parent = elem.getParentNode();
1✔
207
        if (parent != null)
1!
208
            {elem = (Element) parent;}
1✔
209
        final HashMap<String, String> map = new HashMap<>();
1✔
210
        map.put("xml", Namespaces.XML_NS);
1✔
211
        getNamespaceMap(elem, map);
1✔
212
        return map;
1✔
213
    }
214

215
    private void getNamespaceMap(Element elem, Map<String, String> map) {
216
        final NamedNodeMap attrs = elem.getAttributes();
1✔
217
        for(int i = 0; i < attrs.getLength(); i++) {
1✔
218
            final Attr attr = (Attr) attrs.item(i);
1✔
219
            if (attr.getPrefix() != null
1✔
220
                && "xmlns".equals(attr.getPrefix())
1!
221
                && !attr.getValue().equals(CollectionConfiguration.NAMESPACE)
1✔
222
            ) {
223
                map.put(attr.getLocalName(), attr.getValue());
1✔
224
            }
225
        }
226
        Node child = elem.getFirstChild();
1✔
227
        while (child != null) {
1✔
228
            if (child.getNodeType() == Node.ELEMENT_NODE)
1✔
229
                {getNamespaceMap((Element) child, map);}
1✔
230
            child = child.getNextSibling();
1✔
231
        }
232
    }
1✔
233

234
    public String toString() {
235
        final StringBuilder result = new StringBuilder();
×
236
        if (specs!= null) {
×
237
            for (final GeneralRangeIndexSpec spec : specs) {
×
238
                if (spec != null) {
×
239
                    result.append(spec.toString()).append('\n');
×
240
                }
241
            }
242
        }
243
        for (final Map.Entry<QName, QNameRangeIndexSpec> qNameSpec : qnameSpecs.entrySet()) {
×
244
            result.append(qNameSpec.getValue().toString()).append('\n');
×
245
        }
246
        return result.toString();
×
247
    }
248
}
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