View Javadoc
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  package ch.qos.logback.core.model.processor;
15  
16  import java.util.ArrayList;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.function.Supplier;
20  
21  import ch.qos.logback.core.Context;
22  import ch.qos.logback.core.model.Model;
23  import ch.qos.logback.core.model.ModelHandlerFactoryMethod;
24  import ch.qos.logback.core.model.NamedComponentModel;
25  import ch.qos.logback.core.spi.ContextAwareBase;
26  import ch.qos.logback.core.spi.FilterReply;
27  
28  /**
29   * DefaultProcessor traverses the Model produced at an earlier step and performs actual
30   * configuration of logback according to the handlers it was given.
31   *
32   * @author Ceki Gülcü
33   * @since 1.3.0
34   */
35  public class DefaultProcessor extends ContextAwareBase {
36  
37      interface TraverseMethod {
38          int traverse(Model model, ModelFilter modelFiler);
39      }
40  
41      final protected ModelInterpretationContext mic;
42      final HashMap<Class<? extends Model>, ModelHandlerFactoryMethod> modelClassToHandlerMap = new HashMap<>();
43      final HashMap<Class<? extends Model>, List<Supplier<ModelHandlerBase>>> modelClassToDependencyAnalyserMap = new HashMap<>();
44  
45      ChainedModelFilter phaseOneFilter = new ChainedModelFilter();
46      ChainedModelFilter phaseTwoFilter = new ChainedModelFilter();
47  
48      public DefaultProcessor(Context context, ModelInterpretationContext mic) {
49          this.setContext(context);
50          this.mic = mic;
51      }
52  
53      public void addHandler(Class<? extends Model> modelClass, ModelHandlerFactoryMethod modelFactoryMethod) {
54  
55          modelClassToHandlerMap.put(modelClass, modelFactoryMethod);
56  
57          ProcessingPhase phase = determineProcessingPhase(modelClass);
58          switch (phase) {
59              case FIRST:
60                  getPhaseOneFilter().allow(modelClass);
61                  break;
62              case SECOND:
63                  getPhaseTwoFilter().allow(modelClass);
64                  break;
65              default:
66                  throw new IllegalArgumentException("unexpected value " + phase + " for model class " + modelClass.getName());
67          }
68      }
69  
70      private ProcessingPhase determineProcessingPhase(Class<? extends Model> modelClass) {
71  
72          PhaseIndicator phaseIndicator = modelClass.getAnnotation(PhaseIndicator.class);
73          if (phaseIndicator == null) {
74              return ProcessingPhase.FIRST;
75          }
76  
77          ProcessingPhase phase = phaseIndicator.phase();
78          return phase;
79      }
80  
81      public void addAnalyser(Class<? extends Model> modelClass, Supplier<ModelHandlerBase> analyserSupplier) {
82          modelClassToDependencyAnalyserMap.computeIfAbsent(modelClass, x -> new ArrayList<>()).add(analyserSupplier);
83      }
84  
85      private void traversalLoop(TraverseMethod traverseMethod, Model model, ModelFilter modelfFilter, String phaseName) {
86          int LIMIT = 3;
87          for (int i = 0; i < LIMIT; i++) {
88              int handledModelCount = traverseMethod.traverse(model, modelfFilter);
89              if (handledModelCount == 0)
90                  break;
91          }
92      }
93  
94      public void process(Model topModel) {
95  
96          if (topModel == null) {
97              addError("Expecting non null model to process");
98              return;
99          }
100         initialObjectPush();
101 
102         mainTraverse(topModel, getPhaseOneFilter());
103         analyseDependencies(topModel);
104         traversalLoop(this::secondPhaseTraverse, topModel, getPhaseTwoFilter(), "phase 2");
105 
106         addInfo("End of configuration.");
107         finalObjectPop();
108     }
109 
110     private void finalObjectPop() {
111         mic.popObject();
112     }
113 
114     private void initialObjectPush() {
115         mic.pushObject(context);
116     }
117 
118     public ChainedModelFilter getPhaseOneFilter() {
119         return phaseOneFilter;
120     }
121 
122     public ChainedModelFilter getPhaseTwoFilter() {
123         return phaseTwoFilter;
124     }
125 
126 
127     protected void analyseDependencies(Model model) {
128 
129         List<Supplier<ModelHandlerBase>> analyserSupplierList = modelClassToDependencyAnalyserMap.get(model.getClass());
130         ModelHandlerBase analyser = null;
131 
132         if (analyserSupplierList != null) {
133             for (Supplier<ModelHandlerBase> analyserSupplier : analyserSupplierList) {
134 
135                 if (analyserSupplier != null) {
136                     analyser = analyserSupplier.get();
137                 }
138 
139                 if (analyser != null && !model.isSkipped()) {
140                     callAnalyserHandleOnModel(model, analyser);
141                 }
142             }
143         }
144 
145         for (Model m : model.getSubModels()) {
146             analyseDependencies(m);
147         }
148 
149         if (analyser != null && !model.isSkipped()) {
150             callAnalyserPostHandleOnModel(model, analyser);
151         }
152 
153 
154     }
155 
156     private void callAnalyserPostHandleOnModel(Model model, ModelHandlerBase analyser) {
157         try {
158             analyser.postHandle(mic, model);
159         } catch (ModelHandlerException e) {
160             addError("Failed to invoke postHandle on model " + model.getTag(), e);
161         }
162     }
163 
164     private void callAnalyserHandleOnModel(Model model, ModelHandlerBase analyser) {
165         try {
166             analyser.handle(mic, model);
167         } catch (ModelHandlerException e) {
168             addError("Failed to traverse model " + model.getTag(), e);
169         }
170     }
171 
172     static final int DENIED = -1;
173 
174     private ModelHandlerBase createHandler(Model model) {
175         ModelHandlerFactoryMethod modelFactoryMethod = modelClassToHandlerMap.get(model.getClass());
176 
177         if (modelFactoryMethod == null) {
178             addError("Can't handle model of type " + model.getClass() + "  with tag: " + model.getTag() + " at line "
179                     + model.getLineNumber());
180             return null;
181         }
182 
183         ModelHandlerBase handler = modelFactoryMethod.make(context, mic);
184         if (handler == null)
185             return null;
186         if (!handler.isSupportedModelType(model)) {
187             addWarn("Handler [" + handler.getClass() + "] does not support " + model.idString());
188             return null;
189         }
190         return handler;
191     }
192 
193     protected int mainTraverse(Model model, ModelFilter modelFiler) {
194 
195         FilterReply filterReply = modelFiler.decide(model);
196         if (filterReply == FilterReply.DENY)
197             return DENIED;
198 
199         int count = 0;
200 
201         try {
202             ModelHandlerBase handler = null;
203             boolean unhandled = model.isUnhandled();
204 
205             if (unhandled) {
206                 handler = createHandler(model);
207                 if (handler != null) {
208                     handler.handle(mic, model);
209                     model.markAsHandled();
210                     count++;
211                 }
212             }
213             // recurse into submodels handled or not
214             if (!model.isSkipped()) {
215                 for (Model m : model.getSubModels()) {
216                     count += mainTraverse(m, modelFiler);
217                 }
218             }
219 
220             if (unhandled && handler != null) {
221                 handler.postHandle(mic, model);
222             }
223         } catch (ModelHandlerException e) {
224             addError("Failed to traverse model " + model.getTag(), e);
225         }
226         return count;
227     }
228 
229     protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) {
230 
231         FilterReply filterReply = modelFilter.decide(model);
232         if (filterReply == FilterReply.DENY) {
233             return 0;
234         }
235 
236         int count = 0;
237 
238         try {
239 
240             boolean allDependenciesStarted = allDependenciesStarted(model);
241             ModelHandlerBase handler = null;
242             if (model.isUnhandled() && allDependenciesStarted) {
243                 handler = createHandler(model);
244                 if (handler != null) {
245                     handler.handle(mic, model);
246                     model.markAsHandled();
247                     count++;
248                 }
249             }
250 
251             if (!allDependenciesStarted && !dependencyIsLocatedInASubmodel(model)) {
252                 return count;
253             }
254 
255             if (!model.isSkipped()) {
256                 for (Model m : model.getSubModels()) {
257                     count += secondPhaseTraverse(m, modelFilter);
258                 }
259             }
260             if (handler != null) {
261                 handler.postHandle(mic, model);
262             }
263         } catch (ModelHandlerException e) {
264             addError("Failed to traverse model " + model.getTag(), e);
265         }
266         return count;
267     }
268 
269     private boolean dependencyIsLocatedInASubmodel(Model model) {
270         List<String> dependencyNames = this.mic.getDependencyNamesForModel(model);
271         if (dependencyNames == null || dependencyNames.isEmpty()) {
272             return false;
273         }
274 
275         return recursiveIsDependencyPredicate(dependencyNames, model);
276     }
277 
278     private boolean recursiveIsDependencyPredicate(List<String> dependencyNames, Model model) {
279 
280         if (model instanceof NamedComponentModel) {
281             NamedComponentModel namedComponentModel = (NamedComponentModel) model;
282             String modelName = namedComponentModel.getName();
283             if (dependencyNames.contains(modelName)) {
284                 return true;
285             }
286         }
287 
288         for(Model submodel : model.getSubModels()) {
289             boolean result = recursiveIsDependencyPredicate(dependencyNames, submodel);
290             if(result)
291                 return true;
292         }
293 
294         return false;
295     }
296 
297     private boolean allDependenciesStarted(Model model) {
298         // assumes that DependencyDefinitions have been registered
299         List<String> dependencyNames = mic.getDependencyNamesForModel(model);
300 
301         if (dependencyNames == null || dependencyNames.isEmpty()) {
302             return true;
303         }
304         for (String name : dependencyNames) {
305             boolean isRegistered = AppenderDeclarationAnalyser.isAppenderDeclared(mic, name);
306             if (!isRegistered) {
307                 // non registered dependencies are not taken into account
308                 continue;
309             }
310             boolean isStarted = mic.isNamedDependemcyStarted(name);
311             if (!isStarted) {
312                 return false;
313             }
314         }
315         return true;
316     }
317 
318 }