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