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 model) {
095
096        if (model == null) {
097            addError("Expecting non null model to process");
098            return;
099        }
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}