1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2023, 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 v1.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              configureByResource(url);
63              return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
64          } else {
65              return ExecutionStatus.INVOKE_NEXT_IF_ANY;
66          }
67      }
68  
69      private void configureByResource(URL url) {
70          final String urlString = url.toString();
71          if (urlString.endsWith(MODEL_CONFIG_FILE_EXTENSION)) {
72              Model model = retrieveModel(url);
73              if(model == null) {
74                  addWarn("Empty model. Abandoning.");
75                  return;
76              }
77              ModelUtil.resetForReuse(model);
78              buildModelInterpretationContext(model);
79  
80              DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
81              ModelClassToModelHandlerLinker mc2mhl = new ModelClassToModelHandlerLinker(context);
82              mc2mhl.link(defaultProcessor);
83  
84              // disallow simultaneous configurations of the same context
85              ReentrantLock configurationLock   = context.getConfigurationLock();
86              try {
87                  configurationLock.lock();
88                  defaultProcessor.process(model);
89              } finally {
90                  configurationLock.unlock();
91              }
92          } else {
93              throw new LogbackException(
94                      "Unexpected filename extension of file [" + url.toString() + "]. Should be " + MODEL_CONFIG_FILE_EXTENSION);
95          }
96      }
97  
98      private void buildModelInterpretationContext(Model topModel) {
99          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 }