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            configureByResource(url);
063            return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
064        } else {
065            return ExecutionStatus.INVOKE_NEXT_IF_ANY;
066        }
067    }
068
069    private void configureByResource(URL url) {
070        final String urlString = url.toString();
071        if (urlString.endsWith(MODEL_CONFIG_FILE_EXTENSION)) {
072            Model model = retrieveModel(url);
073            if(model == null) {
074                addWarn("Empty model. Abandoning.");
075                return;
076            }
077            ModelUtil.resetForReuse(model);
078            buildModelInterpretationContext(model);
079
080            DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
081            ModelClassToModelHandlerLinker mc2mhl = new ModelClassToModelHandlerLinker(context);
082            mc2mhl.link(defaultProcessor);
083
084            // disallow simultaneous configurations of the same context
085            ReentrantLock configurationLock   = context.getConfigurationLock();
086            try {
087                configurationLock.lock();
088                defaultProcessor.process(model);
089            } finally {
090                configurationLock.unlock();
091            }
092        } else {
093            throw new LogbackException(
094                    "Unexpected filename extension of file [" + url.toString() + "]. Should be " + MODEL_CONFIG_FILE_EXTENSION);
095        }
096    }
097
098    private void buildModelInterpretationContext(Model topModel) {
099        this.modelInterpretationContext = new ModelInterpretationContext(context, this);
100        this.modelInterpretationContext.setTopModel(topModel);
101        LogbackClassicDefaultNestedComponentRules.addDefaultNestedComponentRegistryRules(
102                modelInterpretationContext.getDefaultNestedComponentRegistry());
103        this.modelInterpretationContext.createAppenderBags();
104    }
105
106    private Model retrieveModel(URL url)  {
107        long start = System.currentTimeMillis();
108        try (InputStream is = url.openStream()) {
109            HardenedModelInputStream hmis = new HardenedModelInputStream(is);
110
111            Model model = (Model) hmis.readObject();
112            long diff = System.currentTimeMillis() - start;
113            addInfo("Model at ["+url+"] read in "+diff + " milliseconds");
114            return model;
115        } catch(IOException e) {
116            addError("Failed to open "+url, e);
117        } catch (ClassNotFoundException e) {
118            addError("Failed read model object in "+ url, e);
119        }
120        return null;
121    }
122
123    private URL performMultiStepModelFileSearch(boolean updateState) {
124        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
125        URL url = findModelConfigFileURLFromSystemProperties(myClassLoader);
126        if (url != null) {
127            return url;
128        }
129
130        url = getResource(TEST_AUTOCONFIG_MODEL_FILE, myClassLoader, updateState);
131        if (url != null) {
132            return url;
133        }
134
135        url = getResource(AUTOCONFIG_MODEL_FILE, myClassLoader, updateState);
136        return url;
137    }
138
139    URL findModelConfigFileURLFromSystemProperties(ClassLoader classLoader) {
140        String logbackModelFile = OptionHelper.getSystemProperty(ClassicConstants.MODEL_CONFIG_FILE_PROPERTY);
141
142        if (logbackModelFile != null) {
143            URL result = null;
144            try {
145                result = new URL(logbackModelFile);
146                return result;
147            } catch (MalformedURLException e) {
148                // so, resource is not a URL:
149                // attempt to get the resource from the class path
150                result = Loader.getResource(logbackModelFile, classLoader);
151                if (result != null) {
152                    return result;
153                }
154                File f = new File(logbackModelFile);
155                if (f.exists() && f.isFile()) {
156                    try {
157                        result = f.toURI().toURL();
158                        return result;
159                    } catch (MalformedURLException e1) {
160                    }
161                }
162            } finally {
163                statusOnResourceSearch(logbackModelFile, result);
164            }
165        }
166        return null;
167    }
168
169
170    private URL getResource(String filename, ClassLoader classLoader, boolean updateStatus) {
171        URL url = Loader.getResource(filename, classLoader);
172        if (updateStatus) {
173            statusOnResourceSearch(filename, url);
174        }
175        return url;
176    }
177
178    private void statusOnResourceSearch(String resourceName, URL url) {
179        StatusManager sm = context.getStatusManager();
180        if (url == null) {
181            sm.add(new InfoStatus("Could NOT find resource [" + resourceName + "]", context));
182        } else {
183            sm.add(new InfoStatus("Found resource [" + resourceName + "] at [" + url.toString() + "]", context));
184        }
185    }
186}