• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / sitemesh2 / 59

22 Mar 2026 02:30AM UTC coverage: 40.347%. Remained the same
59

push

github

hazendaz
[mvn] Update maven wrapper

698 of 1891 branches covered (36.91%)

Branch coverage included in aggregate %.

1555 of 3693 relevant lines covered (42.11%)

0.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.3
/src/main/java/com/opensymphony/module/sitemesh/mapper/ConfigLoader.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 * Copyright 2011-2026 Hazendaz
4
 */
5
/*
6
 * Title:        ConfigLoader
7
 * Description:
8
 *
9
 * This software is published under the terms of the OpenSymphony Software
10
 * License version 1.1, of which a copy has been included with this
11
 * distribution in the LICENSE.txt file.
12
 */
13

14
package com.opensymphony.module.sitemesh.mapper;
15

16
import com.opensymphony.module.sitemesh.Config;
17
import com.opensymphony.module.sitemesh.Decorator;
18
import com.opensymphony.module.sitemesh.factory.DefaultFactory;
19

20
import jakarta.servlet.ServletException;
21

22
import java.io.File;
23
import java.io.IOException;
24
import java.nio.file.Path;
25
import java.util.HashMap;
26
import java.util.Map;
27

28
import javax.xml.parsers.DocumentBuilder;
29
import javax.xml.parsers.DocumentBuilderFactory;
30
import javax.xml.parsers.ParserConfigurationException;
31

32
import org.w3c.dom.Document;
33
import org.w3c.dom.Element;
34
import org.w3c.dom.Node;
35
import org.w3c.dom.NodeList;
36
import org.w3c.dom.Text;
37
import org.xml.sax.SAXException;
38

39
/**
40
 * The ConfigLoader reads a configuration XML file that contains Decorator definitions (name, url, init-params) and
41
 * path-mappings (pattern, name).
42
 * <p>
43
 * These can then be accessed by the getDecoratorByName() methods and getMappedName() methods respectively.
44
 * <p>
45
 * The DTD for the configuration file in old (deprecated) format is located at
46
 * <a href="http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd">
47
 * http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd </a>.
48
 * <p>
49
 * The DTD for the configuration file in new format is located at
50
 * <a href="http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd">
51
 * http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd </a>.
52
 * <p>
53
 * Editing the config file will cause it to be auto-reloaded.
54
 * <p>
55
 * This class is used by ConfigDecoratorMapper, and uses PathMapper for pattern matching.
56
 *
57
 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
58
 * @author <a href="mailto:pathos@pandora.be">Mathias Bogaert</a>
59
 *
60
 * @see com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper
61
 * @see com.opensymphony.module.sitemesh.mapper.PathMapper
62
 */
63
public class ConfigLoader {
64

65
    /**
66
     * State visibile across threads stored in a single container so that we can efficiently atomically access it with
67
     * the guarantee that we wont see a partially loaded configuration in the face of one thread reloading the
68
     * configuration while others are trying to read it.
69
     */
70
    private static class State {
1✔
71
        /**
72
         * Timestamp of the last time we checked for an update to the configuration file used to rate limit the
73
         * frequency at which we check for efficiency.
74
         */
75
        long lastModificationCheck = System.currentTimeMillis();
1✔
76

77
        /**
78
         * Timestamp of the modification time of the configuration file when we generated the state.
79
         */
80
        long lastModified;
81

82
        /**
83
         * Whether a thread is currently checking if the configuration file has been modified and potentially reloading
84
         * it and therefore others shouldn't attempt the same till it's done.
85
         */
86
        boolean checking = false;
1✔
87

88
        /** The decorators. */
89
        Map<String, Decorator> decorators = new HashMap<String, Decorator>();
1✔
90

91
        /** The path mapper. */
92
        PathMapper pathMapper = new PathMapper();
1✔
93
    }
94

95
    /**
96
     * Mark volatile so that the installation of new versions is guaranteed to be visible across threads.
97
     */
98
    private volatile State state;
99

100
    /** The config file. */
101
    private File configFile = null;
1✔
102

103
    /** The config file name. */
104
    private String configFileName = null;
1✔
105

106
    /** The config. */
107
    private Config config = null;
1✔
108

109
    /**
110
     * Create new ConfigLoader using supplied File.
111
     *
112
     * @param configFile
113
     *            the config file
114
     *
115
     * @throws ServletException
116
     *             the servlet exception
117
     */
118
    public ConfigLoader(File configFile) throws ServletException {
1✔
119
        this.configFile = configFile;
1✔
120
        this.configFileName = configFile.getName();
1✔
121
        state = loadConfig();
1✔
122
    }
1✔
123

124
    /**
125
     * Create new ConfigLoader using supplied filename and config.
126
     *
127
     * @param configFileName
128
     *            the config file name
129
     * @param config
130
     *            the config
131
     *
132
     * @throws ServletException
133
     *             the servlet exception
134
     */
135
    public ConfigLoader(String configFileName, Config config) throws ServletException {
×
136
        this.config = config;
×
137
        this.configFileName = configFileName;
×
138
        if (config.getServletContext().getRealPath(configFileName) != null) {
×
139
            this.configFile = Path.of(config.getServletContext().getRealPath(configFileName)).toFile();
×
140
        }
141
        state = loadConfig();
×
142
    }
×
143

144
    /**
145
     * Retrieve Decorator based on name specified in configuration file.
146
     *
147
     * @param name
148
     *            the name
149
     *
150
     * @return the decorator by name
151
     *
152
     * @throws ServletException
153
     *             the servlet exception
154
     */
155
    public Decorator getDecoratorByName(String name) throws ServletException {
156
        return (Decorator) refresh().decorators.get(name);
1✔
157
    }
158

159
    /**
160
     * Get name of Decorator mapped to given path.
161
     *
162
     * @param path
163
     *            the path
164
     *
165
     * @return the mapped name
166
     *
167
     * @throws ServletException
168
     *             the servlet exception
169
     */
170
    public String getMappedName(String path) throws ServletException {
171
        return refresh().pathMapper.get(path);
1✔
172
    }
173

174
    /**
175
     * Load configuration from file.
176
     *
177
     * @return the state
178
     *
179
     * @throws ServletException
180
     *             the servlet exception
181
     */
182
    private State loadConfig() throws ServletException {
183
        // The new state which we build up and atomically replace the old state
184
        // with atomically to avoid other threads seeing partial modifications.
185
        State newState = new State();
1✔
186
        try {
187
            // Build a document from the file
188
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1✔
189
            DocumentBuilder builder = factory.newDocumentBuilder();
1✔
190

191
            Document document;
192
            if (configFile != null && configFile.canRead()) {
1!
193
                // Keep time we read the file to check if the file was modified
194
                newState.lastModified = configFile.lastModified();
1✔
195
                document = builder.parse(configFile);
1✔
196
            } else {
197
                document = builder.parse(config.getServletContext().getResourceAsStream(configFileName));
×
198
            }
199

200
            // Parse the configuration document
201
            parseConfig(newState, document);
1✔
202

203
            return newState;
1✔
204
        } catch (ParserConfigurationException e) {
×
205
            throw new ServletException("Could not get XML parser", e);
×
206
        } catch (IOException e) {
×
207
            throw new ServletException("Could not read the config file: " + configFileName, e);
×
208
        } catch (SAXException e) {
×
209
            throw new ServletException("Could not parse the config file: " + configFileName, e);
×
210
        } catch (IllegalArgumentException e) {
×
211
            throw new ServletException("Could not find the config file: " + configFileName, e);
×
212
        }
213
    }
214

215
    /**
216
     * Parses the config.
217
     *
218
     * @param newState
219
     *            the new state
220
     * @param document
221
     *            the document
222
     */
223
    private void parseConfig(State newState, Document document) {
224
        Element root = document.getDocumentElement();
1✔
225

226
        // get the default directory for the decorators
227
        String defaultDir = getAttribute(root, "defaultdir");
1✔
228
        if (defaultDir == null) {
1!
229
            defaultDir = getAttribute(root, "defaultDir");
×
230
        }
231

232
        // Get decorators
233
        NodeList decoratorNodes = root.getElementsByTagName("decorator");
1✔
234
        Element decoratorElement;
235

236
        for (int i = 0; i < decoratorNodes.getLength(); i++) {
1✔
237
            String name, page, uriPath = null, role = null;
1✔
238

239
            // get the current decorator element
240
            decoratorElement = (Element) decoratorNodes.item(i);
1✔
241

242
            if (getAttribute(decoratorElement, "name") != null) {
1✔
243
                // The new format is used
244
                name = getAttribute(decoratorElement, "name");
1✔
245
                page = getAttribute(decoratorElement, "page");
1✔
246
                uriPath = getAttribute(decoratorElement, "webapp");
1✔
247
                role = getAttribute(decoratorElement, "role");
1✔
248

249
                // Append the defaultDir
250
                if (defaultDir != null && page != null && page.length() > 0 && !page.startsWith("/")) {
1!
251
                    if (page.charAt(0) == '/') {
1!
252
                        page = defaultDir + page;
×
253
                    } else {
254
                        page = defaultDir + '/' + page;
1✔
255
                    }
256
                }
257

258
                // The uriPath must begin with a slash
259
                if ((uriPath != null && uriPath.length() > 0) && (uriPath.charAt(0) != '/')) {
1!
260
                    uriPath = '/' + uriPath;
1✔
261
                }
262

263
                // Get all <pattern>...</pattern> and <url-pattern>...</url-pattern> nodes and add a mapping
264
                populatePathMapper(newState, decoratorElement.getElementsByTagName("pattern"), role, name);
1✔
265
                populatePathMapper(newState, decoratorElement.getElementsByTagName("url-pattern"), role, name);
1✔
266
            } else {
267
                // NOTE: Deprecated format
268
                name = getContainedText(decoratorNodes.item(i), "decorator-name");
1✔
269
                page = getContainedText(decoratorNodes.item(i), "resource");
1✔
270
                // We have this here because the use of jsp-file is deprecated, but we still want
271
                // it to work.
272
                if (page == null) {
1!
273
                    page = getContainedText(decoratorNodes.item(i), "jsp-file");
×
274
                }
275
            }
276

277
            Map<Object, Object> params = new HashMap<>();
1✔
278

279
            NodeList paramNodes = decoratorElement.getElementsByTagName("init-param");
1✔
280
            for (int ii = 0; ii < paramNodes.getLength(); ii++) {
1!
281
                String paramName = getContainedText(paramNodes.item(ii), "param-name");
×
282
                String paramValue = getContainedText(paramNodes.item(ii), "param-value");
×
283
                params.put(paramName, paramValue);
×
284
            }
285
            storeDecorator(newState, new DefaultDecorator(name, page, uriPath, role, params));
1✔
286
        }
287

288
        // Get (deprecated format) decorator-mappings
289
        NodeList mappingNodes = root.getElementsByTagName("decorator-mapping");
1✔
290
        for (int i = 0; i < mappingNodes.getLength(); i++) {
1✔
291
            Element n = (Element) mappingNodes.item(i);
1✔
292
            String name = getContainedText(mappingNodes.item(i), "decorator-name");
1✔
293

294
            // Get all <url-pattern>...</url-pattern> nodes and add a mapping
295
            populatePathMapper(newState, n.getElementsByTagName("url-pattern"), null, name);
1✔
296
        }
297
    }
1✔
298

299
    /**
300
     * Populate path mapper.
301
     *
302
     * @param newState
303
     *            the new state
304
     * @param patternNodes
305
     *            the pattern nodes
306
     * @param role
307
     *            the role
308
     * @param name
309
     *            the name
310
     */
311
    private void populatePathMapper(State newState, NodeList patternNodes, String role, String name) {
312
        for (int j = 0; j < patternNodes.getLength(); j++) {
1✔
313
            Element p = (Element) patternNodes.item(j);
1✔
314
            Text patternText = (Text) p.getFirstChild();
1✔
315
            if (patternText != null) {
1✔
316
                String pattern = patternText.getData().trim();
1✔
317
                if (pattern != null) {
1!
318
                    if (role != null) {
1✔
319
                        // concatenate name and role to allow more
320
                        // than one decorator per role
321
                        newState.pathMapper.put(name + role, pattern);
1✔
322
                    } else {
323
                        newState.pathMapper.put(name, pattern);
1✔
324
                    }
325
                }
326
            }
327
        }
328
    }
1✔
329

330
    /**
331
     * Gets the attribute.
332
     *
333
     * @param element
334
     *            the element
335
     * @param name
336
     *            the name
337
     *
338
     * @return the attribute
339
     */
340
    private static String getAttribute(Element element, String name) {
341
        if (element != null && element.getAttribute(name) != null && !"".equals(element.getAttribute(name).trim())) {
1!
342
            return element.getAttribute(name).trim();
1✔
343
        }
344
        return null;
1✔
345
    }
346

347
    /**
348
     * Gets the contained text.
349
     *
350
     * @param parent
351
     *            the parent
352
     * @param childTagName
353
     *            the child tag name
354
     *
355
     * @return the contained text
356
     */
357
    private static String getContainedText(Node parent, String childTagName) {
358
        try {
359
            Node tag = ((Element) parent).getElementsByTagName(childTagName).item(0);
1✔
360
            return ((Text) tag.getFirstChild()).getData();
1✔
361
        } catch (Exception e) {
×
362
            return null;
×
363
        }
364
    }
365

366
    /**
367
     * Store decorator.
368
     *
369
     * @param newState
370
     *            the new state
371
     * @param d
372
     *            the d
373
     */
374
    private void storeDecorator(State newState, Decorator d) {
375
        if (d.getRole() != null) {
1✔
376
            newState.decorators.put(d.getName() + d.getRole(), d);
1✔
377
        } else {
378
            newState.decorators.put(d.getName(), d);
1✔
379
        }
380
    }
1✔
381

382
    /**
383
     * Check if configuration file has been updated, and if so, reload.
384
     *
385
     * @return the state
386
     *
387
     * @throws ServletException
388
     *             the servlet exception
389
     */
390
    private State refresh() throws ServletException {
391
        // Read the current state just once since another thread can swap
392
        // another version in at any time.
393
        State currentState = state;
1✔
394
        if (configFile == null) {
1!
395
            return currentState;
×
396
        }
397

398
        // Rate limit the stat'ing of the config file to find its
399
        // modification time to once every five seconds to reduce the
400
        // number of system calls made. We grab the monitor of currentState
401
        // so that we can safely read the values shared across threads and
402
        // so that only one thread is performing the modification check at
403
        // a time.
404
        long current = System.currentTimeMillis();
1✔
405
        long oldLastModified;
406

407
        boolean check = false;
1✔
408
        synchronized (currentState) {
1✔
409
            oldLastModified = currentState.lastModified;
1✔
410
            if (!currentState.checking
1!
411
                    && current >= currentState.lastModificationCheck + DefaultFactory.configCheckMillis) {
412
                currentState.lastModificationCheck = current;
×
413
                currentState.checking = true;
×
414
                check = true;
×
415
            }
416
        }
1✔
417

418
        if (check) {
1!
419
            // Perform the file stat'ing system call without holding a lock
420
            // on the current state.
421
            State newState = null;
×
422
            try {
423
                long currentLastModified = configFile.lastModified();
×
424
                if (currentLastModified != oldLastModified) {
×
425
                    // The configuration file has been modified since we last
426
                    // read it so reload the configuration without holding a
427
                    // lock on the current state and then slam down the new
428
                    // state for other threads to see. The State.checking flag
429
                    // being set on currentState will prevent other threads
430
                    // from attempting to reload the state while we are and
431
                    // the new state will have the flag cleared so that we can
432
                    // continue checking if the configuration file is modified.
433
                    newState = loadConfig();
×
434
                    state = newState;
×
435
                    return newState;
×
436
                }
437
            } finally {
438
                // In the event of a failure of the modification time check,
439
                // or while reloading the configuration file, mark that we're no
440
                // longer checking if the modification time or reloading so that
441
                // we'll retry.
442
                if (newState == null) {
×
443
                    synchronized (currentState) {
×
444
                        currentState.checking = false;
×
445
                    }
×
446
                }
447
            }
448

449
        }
450
        return currentState;
1✔
451
    }
452
}
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

© 2026 Coveralls, Inc