001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014
015package ch.qos.logback.core.model.processor;
016
017import ch.qos.logback.core.Context;
018import ch.qos.logback.core.joran.GenericXMLConfigurator;
019import ch.qos.logback.core.joran.event.SaxEvent;
020import ch.qos.logback.core.joran.event.SaxEventRecorder;
021import ch.qos.logback.core.joran.spi.JoranException;
022import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
023import ch.qos.logback.core.model.IncludeModel;
024import ch.qos.logback.core.model.Model;
025import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
026import ch.qos.logback.core.spi.ErrorCodes;
027import ch.qos.logback.core.util.Loader;
028import ch.qos.logback.core.util.OptionHelper;
029
030import java.io.File;
031import java.io.IOException;
032import java.io.InputStream;
033import java.net.MalformedURLException;
034import java.net.URI;
035import java.net.URL;
036import java.util.List;
037import java.util.function.Supplier;
038
039import static ch.qos.logback.core.joran.JoranConstants.CONFIGURATION_TAG;
040import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG;
041
042/**
043 * @since 1.5.5
044 */
045public class IncludeModelHandler extends ResourceHandlerBase {
046    boolean inError = false;
047
048    public IncludeModelHandler(Context context) {
049        super(context);
050    }
051
052    static public IncludeModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
053        return new IncludeModelHandler(context);
054    }
055
056    @Override
057    protected Class<IncludeModel> getSupportedModelClass() {
058        return IncludeModel.class;
059    }
060
061    @Override
062    public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
063        IncludeModel includeModel = (IncludeModel) model;
064
065        URL topURL = mic.getTopURL();
066        Boolean topScan = mic.getTopScanBoolean();
067        Model modelFromIncludedFile = buildModelFromIncludedFile(mic, topURL, topScan, includeModel);
068        if (modelFromIncludedFile == null) {
069            warnIfRequired("Failed to build include model from included file");
070            return;
071        }
072        processModelFromIncludedFile(includeModel, modelFromIncludedFile);
073    }
074
075    /**
076     * This method is called by logback-tyler at TylerConfigurator run-time.
077     *
078     * @param capc
079     * @param includeModel
080     * @throws ModelHandlerException
081     * @since 1.5.11
082     */
083    public Model buildModelFromIncludedFile(ContextAwarePropertyContainer capc, URL topURL, Boolean topScan, IncludeModel includeModel) throws ModelHandlerException {
084
085        this.optional = OptionHelper.toBoolean(includeModel.getOptional(), false);
086
087        if (!checkAttributes(includeModel)) {
088            inError = true;
089            return null;
090        }
091
092
093        URL inputURL = getInputURL(capc, includeModel);
094        if (inputURL == null) {
095            inError = true;
096            return null;
097        }
098
099        // allow for the creation of the URL at a later time
100        // updateConfigurationWatchList should be invoked before attempting to open the URL
101        updateConfigurationWatchList(inputURL, topURL, topScan);
102
103        InputStream in = openURL(inputURL);
104        if (in == null) {
105            inError = true;
106            return null;
107        }
108
109        addInfo("Including configuration file [" + inputURL + "]");
110
111        SaxEventRecorder recorder = null;
112
113        try {
114            recorder = populateSaxEventRecorder(in);
115
116            List<SaxEvent> saxEvents = recorder.getSaxEventList();
117            if (saxEvents.isEmpty()) {
118                addWarn("Empty sax event list");
119                return null;
120            }
121
122            Supplier<? extends GenericXMLConfigurator> jcSupplier = capc.getConfiguratorSupplier();
123            if (jcSupplier == null) {
124                addError("null configurator supplier. Abandoning inclusion of [" + attributeInUse + "]");
125                inError = true;
126                return null;
127            }
128
129            GenericXMLConfigurator genericXMLConfigurator = jcSupplier.get();
130            genericXMLConfigurator.getRuleStore().addPathPathMapping(INCLUDED_TAG, CONFIGURATION_TAG);
131
132            Model modelFromIncludedFile = genericXMLConfigurator.buildModelFromSaxEventList(recorder.getSaxEventList());
133            return modelFromIncludedFile;
134        } catch (JoranException e) {
135            inError = true;
136            addError("Error processing XML data in [" + attributeInUse + "]", e);
137            return null;
138        }
139    }
140
141    private void processModelFromIncludedFile(IncludeModel includeModel, Model modelFromIncludedFile) {
142        includeModel.getSubModels().addAll(modelFromIncludedFile.getSubModels());
143    }
144
145    public SaxEventRecorder populateSaxEventRecorder(final InputStream inputStream) throws JoranException {
146        SaxEventRecorder recorder = new SaxEventRecorder(context);
147        recorder.recordEvents(inputStream);
148        return recorder;
149    }
150
151    private void updateConfigurationWatchList(URL inputURL, URL topURL, Boolean topScan) throws ModelHandlerException {
152        if(topScan == Boolean.TRUE) {
153            if(topURL != null) {
154                ConfigurationWatchListUtil.addToWatchList(context, inputURL);
155            } else {
156                addWarn("No top URL for XML configuration file. Will not add [" + inputURL + "] to watch list.");
157            }
158        }
159
160    }
161
162}