001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2023, 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 v1.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.classic.joran;
016
017import ch.qos.logback.classic.ClassicConstants;
018import ch.qos.logback.classic.LoggerContext;
019import ch.qos.logback.classic.joran.serializedModel.HardenedModelInputStream;
020import ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules;
021import ch.qos.logback.classic.spi.ConfiguratorRank;
022import ch.qos.logback.core.Context;
023import ch.qos.logback.core.LogbackException;
024import ch.qos.logback.core.model.Model;
025import ch.qos.logback.core.model.ModelUtil;
026import ch.qos.logback.core.model.processor.DefaultProcessor;
027import ch.qos.logback.core.model.processor.ModelInterpretationContext;
028import ch.qos.logback.classic.spi.Configurator;
029import ch.qos.logback.core.spi.ContextAwareBase;
030import ch.qos.logback.core.status.InfoStatus;
031import ch.qos.logback.core.status.StatusManager;
032import ch.qos.logback.core.util.Loader;
033import ch.qos.logback.core.util.OptionHelper;
034
035import java.io.File;
036import java.io.IOException;
037import java.io.InputStream;
038import java.net.MalformedURLException;
039import java.net.URL;
040import java.util.concurrent.locks.ReentrantLock;
041
042import static ch.qos.logback.core.CoreConstants.MODEL_CONFIG_FILE_EXTENSION;
043
044/**
045 * @since 1.3.9/1.4.9
046 */
047
048// BEWARE: the fqcn is used in SerializedModelModelHandler
049@ConfiguratorRank(value = ConfiguratorRank.SERIALIZED_MODEL)
050public class SerializedModelConfigurator extends ContextAwareBase implements Configurator {
051
052    final public static String AUTOCONFIG_MODEL_FILE = "logback"+ MODEL_CONFIG_FILE_EXTENSION;
053
054    final public static String TEST_AUTOCONFIG_MODEL_FILE = "logback-test"+ MODEL_CONFIG_FILE_EXTENSION;
055    protected ModelInterpretationContext modelInterpretationContext;
056
057    @Override
058    public ExecutionStatus configure(LoggerContext loggerContext) {
059
060        URL url = performMultiStepModelFileSearch(true);
061        if (url != null) {
062            addWarn("Replaced by logback-tyler, SerializedModelConfigurator has been deprecated and will be removed on 2025-07-01.");
063            configureByResource(url);
064            return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
065        } else {
066            return ExecutionStatus.INVOKE_NEXT_IF_ANY;
067        }
068    }
069
070    private void configureByResource(URL url) {
071        final String urlString = url.toString();
072        if (urlString.endsWith(MODEL_CONFIG_FILE_EXTENSION)) {
073            Model model = retrieveModel(url);
074            if(model == null) {
075                addWarn("Empty model. Abandoning.");
076                return;
077            }
078            ModelUtil.resetForReuse(model);
079            buildModelInterpretationContext(model);
080
081            DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
082            ModelClassToModelHandlerLinker mc2mhl = new ModelClassToModelHandlerLinker(context);
083            mc2mhl.link(defaultProcessor);
084
085            // disallow simultaneous configurations of the same context
086            ReentrantLock configurationLock   = context.getConfigurationLock();
087            try {
088                configurationLock.lock();
089                defaultProcessor.process(model);
090            } finally {
091                configurationLock.unlock();
092            }
093        } else {
094            throw new LogbackException(
095                    "Unexpected filename extension of file [" + url.toString() + "]. Should be " + MODEL_CONFIG_FILE_EXTENSION);
096        }
097    }
098
099    private void buildModelInterpretationContext(Model topModel) {
100        this.modelInterpretationContext = new ModelInterpretationContext(context, this);
101        this.modelInterpretationContext.setTopModel(topModel);
102        LogbackClassicDefaultNestedComponentRules.addDefaultNestedComponentRegistryRules(
103                modelInterpretationContext.getDefaultNestedComponentRegistry());
104        this.modelInterpretationContext.createAppenderBags();
105    }
106
107    private Model retrieveModel(URL url)  {
108        long start = System.currentTimeMillis();
109        try (InputStream is = url.openStream()) {
110            HardenedModelInputStream hmis = new HardenedModelInputStream(is);
111
112            Model model = (Model) hmis.readObject();
113            long diff = System.currentTimeMillis() - start;
114            addInfo("Model at ["+url+"] read in "+diff + " milliseconds");
115            return model;
116        } catch(IOException e) {
117            addError("Failed to open "+url, e);
118        } catch (ClassNotFoundException e) {
119            addError("Failed read model object in "+ url, e);
120        }
121        return null;
122    }
123
124    private URL performMultiStepModelFileSearch(boolean updateState) {
125        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
126        URL url = findModelConfigFileURLFromSystemProperties(myClassLoader);
127        if (url != null) {
128            return url;
129        }
130
131        url = getResource(TEST_AUTOCONFIG_MODEL_FILE, myClassLoader, updateState);
132        if (url != null) {
133            return url;
134        }
135
136        url = getResource(AUTOCONFIG_MODEL_FILE, myClassLoader, updateState);
137        return url;
138    }
139
140    URL findModelConfigFileURLFromSystemProperties(ClassLoader classLoader) {
141        String logbackModelFile = OptionHelper.getSystemProperty(ClassicConstants.MODEL_CONFIG_FILE_PROPERTY);
142
143        if (logbackModelFile != null) {
144            URL result = null;
145            try {
146                result = new URL(logbackModelFile);
147                return result;
148            } catch (MalformedURLException e) {
149                // so, resource is not a URL:
150                // attempt to get the resource from the class path
151                result = Loader.getResource(logbackModelFile, classLoader);
152                if (result != null) {
153                    return result;
154                }
155                File f = new File(logbackModelFile);
156                if (f.exists() && f.isFile()) {
157                    try {
158                        result = f.toURI().toURL();
159                        return result;
160                    } catch (MalformedURLException e1) {
161                    }
162                }
163            } finally {
164                statusOnResourceSearch(logbackModelFile, result);
165            }
166        }
167        return null;
168    }
169
170
171    private URL getResource(String filename, ClassLoader classLoader, boolean updateStatus) {
172        URL url = Loader.getResource(filename, classLoader);
173        if (updateStatus) {
174            statusOnResourceSearch(filename, url);
175        }
176        return url;
177    }
178
179    private void statusOnResourceSearch(String resourceName, URL url) {
180        StatusManager sm = context.getStatusManager();
181        if (url == null) {
182            sm.add(new InfoStatus("Could NOT find resource [" + resourceName + "]", context));
183        } else {
184            sm.add(new InfoStatus("Found resource [" + resourceName + "] at [" + url.toString() + "]", context));
185        }
186    }
187}