IncludeModelHandler.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */

package ch.qos.logback.core.model.processor;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.GenericXMLConfigurator;
import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.event.SaxEventRecorder;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
import ch.qos.logback.core.model.IncludeModel;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
import ch.qos.logback.core.spi.ErrorCodes;
import ch.qos.logback.core.util.Loader;
import ch.qos.logback.core.util.OptionHelper;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.function.Supplier;

import static ch.qos.logback.core.joran.JoranConstants.CONFIGURATION_TAG;
import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG;

/**
 * @since 1.5.5
 */
public class IncludeModelHandler extends ResourceHandlerBase {
    boolean inError = false;

    public IncludeModelHandler(Context context) {
        super(context);
    }

    static public IncludeModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
        return new IncludeModelHandler(context);
    }

    @Override
    protected Class<IncludeModel> getSupportedModelClass() {
        return IncludeModel.class;
    }

    @Override
    public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
        IncludeModel includeModel = (IncludeModel) model;

        URL topURL = mic.getTopURL();
        Boolean topScan = mic.getTopScanBoolean();
        Model modelFromIncludedFile = buildModelFromIncludedFile(mic, topURL, topScan, includeModel);
        if (modelFromIncludedFile == null) {
            warnIfRequired("Failed to build include model from included file");
            return;
        }
        processModelFromIncludedFile(includeModel, modelFromIncludedFile);
    }

    /**
     * This method is called by logback-tyler at TylerConfigurator run-time.
     *
     * @param capc
     * @param includeModel
     * @throws ModelHandlerException
     * @since 1.5.11
     */
    public Model buildModelFromIncludedFile(ContextAwarePropertyContainer capc, URL topURL, Boolean topScan, IncludeModel includeModel) throws ModelHandlerException {

        this.optional = OptionHelper.toBoolean(includeModel.getOptional(), false);

        if (!checkAttributes(includeModel)) {
            inError = true;
            return null;
        }


        URL inputURL = getInputURL(capc, includeModel);
        if (inputURL == null) {
            inError = true;
            return null;
        }

        // allow for the creation of the URL at a later time
        // updateConfigurationWatchList should be invoked before attempting to open the URL
        updateConfigurationWatchList(inputURL, topURL, topScan);

        InputStream in = openURL(inputURL);
        if (in == null) {
            inError = true;
            return null;
        }

        addInfo("Including configuration file [" + inputURL + "]");

        SaxEventRecorder recorder = null;

        try {
            recorder = populateSaxEventRecorder(in);

            List<SaxEvent> saxEvents = recorder.getSaxEventList();
            if (saxEvents.isEmpty()) {
                addWarn("Empty sax event list");
                return null;
            }

            Supplier<? extends GenericXMLConfigurator> jcSupplier = capc.getConfiguratorSupplier();
            if (jcSupplier == null) {
                addError("null configurator supplier. Abandoning inclusion of [" + attributeInUse + "]");
                inError = true;
                return null;
            }

            GenericXMLConfigurator genericXMLConfigurator = jcSupplier.get();
            genericXMLConfigurator.getRuleStore().addPathPathMapping(INCLUDED_TAG, CONFIGURATION_TAG);

            Model modelFromIncludedFile = genericXMLConfigurator.buildModelFromSaxEventList(recorder.getSaxEventList());
            return modelFromIncludedFile;
        } catch (JoranException e) {
            inError = true;
            addError("Error processing XML data in [" + attributeInUse + "]", e);
            return null;
        }
    }

    private void processModelFromIncludedFile(IncludeModel includeModel, Model modelFromIncludedFile) {
        includeModel.getSubModels().addAll(modelFromIncludedFile.getSubModels());
    }

    public SaxEventRecorder populateSaxEventRecorder(final InputStream inputStream) throws JoranException {
        SaxEventRecorder recorder = new SaxEventRecorder(context);
        recorder.recordEvents(inputStream);
        return recorder;
    }

    private void updateConfigurationWatchList(URL inputURL, URL topURL, Boolean topScan) throws ModelHandlerException {
        if(topScan == Boolean.TRUE) {
            if(topURL != null) {
                ConfigurationWatchListUtil.addToWatchList(context, inputURL);
            } else {
                addWarn("No top URL for XML configuration file. Will not add [" + inputURL + "] to watch list.");
            }
        }

    }

}