1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.classic.joran;
16  
17  import ch.qos.logback.classic.ClassicConstants;
18  import ch.qos.logback.classic.LoggerContext;
19  import ch.qos.logback.classic.joran.serializedModel.HardenedModelInputStream;
20  import ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules;
21  import ch.qos.logback.classic.spi.ConfiguratorRank;
22  import ch.qos.logback.core.Context;
23  import ch.qos.logback.core.LogbackException;
24  import ch.qos.logback.core.model.Model;
25  import ch.qos.logback.core.model.ModelUtil;
26  import ch.qos.logback.core.model.processor.DefaultProcessor;
27  import ch.qos.logback.core.model.processor.ModelInterpretationContext;
28  import ch.qos.logback.classic.spi.Configurator;
29  import ch.qos.logback.core.spi.ContextAwareBase;
30  import ch.qos.logback.core.status.InfoStatus;
31  import ch.qos.logback.core.status.StatusManager;
32  import ch.qos.logback.core.util.Loader;
33  import ch.qos.logback.core.util.OptionHelper;
34  
35  import java.io.File;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.net.MalformedURLException;
39  import java.net.URL;
40  import java.util.concurrent.locks.ReentrantLock;
41  
42  import static ch.qos.logback.core.CoreConstants.MODEL_CONFIG_FILE_EXTENSION;
43  
44  /**
45   * @since 1.3.9/1.4.9
46   */
47  
48  // BEWARE: the fqcn is used in SerializedModelModelHandler
49  @ConfiguratorRank(value = ConfiguratorRank.SERIALIZED_MODEL)
50  public class SerializedModelConfigurator extends ContextAwareBase implements Configurator {
51  
52      final public static String AUTOCONFIG_MODEL_FILE = "logback"+ MODEL_CONFIG_FILE_EXTENSION;
53  
54      final public static String TEST_AUTOCONFIG_MODEL_FILE = "logback-test"+ MODEL_CONFIG_FILE_EXTENSION;
55      protected ModelInterpretationContext modelInterpretationContext;
56  
57      @Override
58      public ExecutionStatus configure(LoggerContext loggerContext) {
59  
60          URL url = performMultiStepModelFileSearch(true);
61          if (url != null) {
62              addWarn("Replaced by logback-tyler, SerializedModelConfigurator has been deprecated and will be removed on 2025-07-01.");
63              configureByResource(url);
64              return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
65          } else {
66              return ExecutionStatus.INVOKE_NEXT_IF_ANY;
67          }
68      }
69  
70      private void configureByResource(URL url) {
71          final String urlString = url.toString();
72          if (urlString.endsWith(MODEL_CONFIG_FILE_EXTENSION)) {
73              Model model = retrieveModel(url);
74              if(model == null) {
75                  addWarn("Empty model. Abandoning.");
76                  return;
77              }
78              ModelUtil.resetForReuse(model);
79              buildModelInterpretationContext(model);
80  
81              DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
82              ModelClassToModelHandlerLinker mc2mhl = new ModelClassToModelHandlerLinker(context);
83              mc2mhl.link(defaultProcessor);
84  
85              // disallow simultaneous configurations of the same context
86              ReentrantLock configurationLock   = context.getConfigurationLock();
87              try {
88                  configurationLock.lock();
89                  defaultProcessor.process(model);
90              } finally {
91                  configurationLock.unlock();
92              }
93          } else {
94              throw new LogbackException(
95                      "Unexpected filename extension of file [" + url.toString() + "]. Should be " + MODEL_CONFIG_FILE_EXTENSION);
96          }
97      }
98  
99      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 }