001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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 */
014package ch.qos.logback.core.model.processor;
015
016import java.util.ArrayList;
017import java.util.HashMap;
018import java.util.List;
019import java.util.function.Supplier;
020
021import ch.qos.logback.core.Context;
022import ch.qos.logback.core.model.Model;
023import ch.qos.logback.core.model.ModelHandlerFactoryMethod;
024import ch.qos.logback.core.model.NamedComponentModel;
025import ch.qos.logback.core.spi.ContextAwareBase;
026import ch.qos.logback.core.spi.FilterReply;
027
028/**
029 * DefaultProcessor traverses the Model produced at an earlier step and performs actual
030 * configuration of logback according to the handlers it was given.
031 *
032 * @author Ceki Gülcü
033 * @since 1.3.0
034 */
035public class DefaultProcessor extends ContextAwareBase {
036
037    interface TraverseMethod {
038        int traverse(Model model, ModelFilter modelFiler);
039    }
040
041    final protected ModelInterpretationContext mic;
042    final HashMap<Class<? extends Model>, ModelHandlerFactoryMethod> modelClassToHandlerMap = new HashMap<>();
043    final HashMap<Class<? extends Model>, List<Supplier<ModelHandlerBase>>> modelClassToDependencyAnalyserMap = new HashMap<>();
044
045    ChainedModelFilter phaseOneFilter = new ChainedModelFilter();
046    ChainedModelFilter phaseTwoFilter = new ChainedModelFilter();
047
048    public DefaultProcessor(Context context, ModelInterpretationContext mic) {
049        this.setContext(context);
050        this.mic = mic;
051    }
052
053    public void addHandler(Class<? extends Model> modelClass, ModelHandlerFactoryMethod modelFactoryMethod) {
054
055        modelClassToHandlerMap.put(modelClass, modelFactoryMethod);
056
057        ProcessingPhase phase = determineProcessingPhase(modelClass);
058        switch (phase) {
059            case FIRST:
060                getPhaseOneFilter().allow(modelClass);
061                break;
062            case SECOND:
063                getPhaseTwoFilter().allow(modelClass);
064                break;
065            default:
066                throw new IllegalArgumentException("unexpected value " + phase + " for model class " + modelClass.getName());
067        }
068    }
069
070    private ProcessingPhase determineProcessingPhase(Class<? extends Model> modelClass) {
071
072        PhaseIndicator phaseIndicator = modelClass.getAnnotation(PhaseIndicator.class);
073        if (phaseIndicator == null) {
074            return ProcessingPhase.FIRST;
075        }
076
077        ProcessingPhase phase = phaseIndicator.phase();
078        return phase;
079    }
080
081    public void addAnalyser(Class<? extends Model> modelClass, Supplier<ModelHandlerBase> analyserSupplier) {
082        modelClassToDependencyAnalyserMap.computeIfAbsent(modelClass, x -> new ArrayList<>()).add(analyserSupplier);
083    }
084
085    private void traversalLoop(TraverseMethod traverseMethod, Model model, ModelFilter modelfFilter, String phaseName) {
086        int LIMIT = 3;
087        for (int i = 0; i < LIMIT; i++) {
088            int handledModelCount = traverseMethod.traverse(model, modelfFilter);
089            if (handledModelCount == 0)
090                break;
091        }
092    }
093
094    public void process(Model topModel) {
095
096        if (topModel == null) {
097            addError("Expecting non null model to process");
098            return;
099        }
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}