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 model) {
95  
96          if (model == null) {
97              addError("Expecting non null model to process");
98              return;
99          }
100         initialObjectPush();
101 
102         mainTraverse(model, getPhaseOneFilter());
103         analyseDependencies(model);
104         traversalLoop(this::secondPhaseTraverse, model, 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 
131         if (analyserSupplierList != null) {
132             for (Supplier<ModelHandlerBase> analyserSupplier : analyserSupplierList) {
133                 ModelHandlerBase analyser = null;
134 
135                 if (analyserSupplier != null) {
136                     analyser = analyserSupplier.get();
137                 }
138 
139                 if (analyser != null && !model.isSkipped()) {
140                     callAnalyserHandleOnModel(model, analyser);
141                 }
142 
143                 if (analyser != null && !model.isSkipped()) {
144                     callAnalyserPostHandleOnModel(model, analyser);
145                 }
146             }
147         }
148 
149         for (Model m : model.getSubModels()) {
150             analyseDependencies(m);
151         }
152 
153     }
154 
155     private void callAnalyserPostHandleOnModel(Model model, ModelHandlerBase analyser) {
156         try {
157             analyser.postHandle(mic, model);
158         } catch (ModelHandlerException e) {
159             addError("Failed to invoke postHandle on model " + model.getTag(), e);
160         }
161     }
162 
163     private void callAnalyserHandleOnModel(Model model, ModelHandlerBase analyser) {
164         try {
165             analyser.handle(mic, model);
166         } catch (ModelHandlerException e) {
167             addError("Failed to traverse model " + model.getTag(), e);
168         }
169     }
170 
171     static final int DENIED = -1;
172 
173     private ModelHandlerBase createHandler(Model model) {
174         ModelHandlerFactoryMethod modelFactoryMethod = modelClassToHandlerMap.get(model.getClass());
175 
176         if (modelFactoryMethod == null) {
177             addError("Can't handle model of type " + model.getClass() + "  with tag: " + model.getTag() + " at line "
178                     + model.getLineNumber());
179             return null;
180         }
181 
182         ModelHandlerBase handler = modelFactoryMethod.make(context, mic);
183         if (handler == null)
184             return null;
185         if (!handler.isSupportedModelType(model)) {
186             addWarn("Handler [" + handler.getClass() + "] does not support " + model.idString());
187             return null;
188         }
189         return handler;
190     }
191 
192     protected int mainTraverse(Model model, ModelFilter modelFiler) {
193 
194         FilterReply filterReply = modelFiler.decide(model);
195         if (filterReply == FilterReply.DENY)
196             return DENIED;
197 
198         int count = 0;
199 
200         try {
201             ModelHandlerBase handler = null;
202             boolean unhandled = model.isUnhandled();
203 
204             if (unhandled) {
205                 handler = createHandler(model);
206                 if (handler != null) {
207                     handler.handle(mic, model);
208                     model.markAsHandled();
209                     count++;
210                 }
211             }
212             // recurse into submodels handled or not
213             if (!model.isSkipped()) {
214                 for (Model m : model.getSubModels()) {
215                     count += mainTraverse(m, modelFiler);
216                 }
217             }
218 
219             if (unhandled && handler != null) {
220                 handler.postHandle(mic, model);
221             }
222         } catch (ModelHandlerException e) {
223             addError("Failed to traverse model " + model.getTag(), e);
224         }
225         return count;
226     }
227 
228     protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) {
229 
230         FilterReply filterReply = modelFilter.decide(model);
231         if (filterReply == FilterReply.DENY) {
232             return 0;
233         }
234 
235         int count = 0;
236 
237         try {
238 
239             boolean allDependenciesStarted = allDependenciesStarted(model);
240 
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 && !dependencyIsADirectSubmodel(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 dependencyIsADirectSubmodel(Model model) {
270         List<String> dependecyNames = this.mic.getDependencyNamesForModel(model);
271         if (dependecyNames == null || dependecyNames.isEmpty()) {
272             return false;
273         }
274         for (Model submodel : model.getSubModels()) {
275             if (submodel instanceof NamedComponentModel) {
276                 NamedComponentModel namedComponentModel = (NamedComponentModel) submodel;
277                 String subModelName = namedComponentModel.getName();
278                 if (dependecyNames.contains(subModelName)) {
279                     return true;
280                 }
281             }
282         }
283 
284         return false;
285     }
286 
287     private boolean allDependenciesStarted(Model model) {
288         // assumes that DependencyDefinitions have been registered
289         List<String> dependencyNames = mic.getDependencyNamesForModel(model);
290 
291         if (dependencyNames == null || dependencyNames.isEmpty()) {
292             return true;
293         }
294         for (String name : dependencyNames) {
295             boolean isRegistered = AppenderDeclarationAnalyser.isAppenderDeclared(mic, name);
296             if (!isRegistered) {
297                 // non registered dependencies are not taken into account
298                 continue;
299             }
300             boolean isStarted = mic.isNamedDependemcyStarted(name);
301             if (!isStarted) {
302                 return false;
303             }
304         }
305         return true;
306     }
307 
308 }