001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
004 * <p>
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 * <p>
009 * or (per the licensee's choosing)
010 * <p>
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.lang.reflect.Constructor;
017import java.lang.reflect.InvocationTargetException;
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.function.Supplier;
022
023import ch.qos.logback.core.Context;
024import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
025import ch.qos.logback.core.model.Model;
026import ch.qos.logback.core.model.ModelHandlerFactoryMethod;
027import ch.qos.logback.core.model.NamedComponentModel;
028import ch.qos.logback.core.spi.ContextAwareBase;
029import ch.qos.logback.core.spi.FilterReply;
030
031/**
032 * DefaultProcessor traverses the Model produced at an earlier step and performs actual
033 * configuration of logback according to the handlers it was given.
034 *
035 * @author Ceki G&uuml;lc&uuml;
036 * @since 1.3.0
037 */
038public class DefaultProcessor extends ContextAwareBase {
039
040    interface TraverseMethod {
041        int traverse(Model model, ModelFilter modelFiler);
042    }
043
044    final protected ModelInterpretationContext mic;
045    final HashMap<Class<? extends Model>, ModelHandlerFactoryMethod> modelClassToHandlerMap = new HashMap<>();
046    final HashMap<Class<? extends Model>, List<Supplier<ModelHandlerBase>>> modelClassToDependencyAnalyserMap = new HashMap<>();
047
048    ChainedModelFilter phaseOneFilter = new ChainedModelFilter();
049    ChainedModelFilter phaseTwoFilter = new ChainedModelFilter();
050
051    public DefaultProcessor(Context context, ModelInterpretationContext mic) {
052        this.setContext(context);
053        this.mic = mic;
054    }
055
056    public void addHandler(Class<? extends Model> modelClass, ModelHandlerFactoryMethod modelFactoryMethod) {
057
058        modelClassToHandlerMap.put(modelClass, modelFactoryMethod);
059
060        ProcessingPhase phase = determineProcessingPhase(modelClass);
061        switch (phase) {
062            case FIRST:
063                getPhaseOneFilter().allow(modelClass);
064                break;
065            case SECOND:
066                getPhaseTwoFilter().allow(modelClass);
067                break;
068            default:
069                throw new IllegalArgumentException("unexpected value " + phase + " for model class " + modelClass.getName());
070        }
071    }
072
073    private ProcessingPhase determineProcessingPhase(Class<? extends Model> modelClass) {
074
075        PhaseIndicator phaseIndicator = modelClass.getAnnotation(PhaseIndicator.class);
076        if (phaseIndicator == null) {
077            return ProcessingPhase.FIRST;
078        }
079
080        ProcessingPhase phase = phaseIndicator.phase();
081        return phase;
082    }
083
084    public void addAnalyser(Class<? extends Model> modelClass, Supplier<ModelHandlerBase> analyserSupplier) {
085        modelClassToDependencyAnalyserMap.computeIfAbsent(modelClass, x -> new ArrayList<>()).add(analyserSupplier);
086    }
087
088    private void traversalLoop(TraverseMethod traverseMethod, Model model, ModelFilter modelfFilter, String phaseName) {
089        int LIMIT = 3;
090        for (int i = 0; i < LIMIT; i++) {
091            int handledModelCount = traverseMethod.traverse(model, modelfFilter);
092            if (handledModelCount == 0)
093                break;
094        }
095    }
096
097    public void process(Model model) {
098
099        if (model == null) {
100            addError("Expecting non null model to process");
101            return;
102        }
103        initialObjectPush();
104
105        mainTraverse(model, getPhaseOneFilter());
106        analyseDependencies(model);
107        traversalLoop(this::secondPhaseTraverse, model, getPhaseTwoFilter(), "phase 2");
108
109        addInfo("End of configuration.");
110        finalObjectPop();
111    }
112
113    private void finalObjectPop() {
114        mic.popObject();
115    }
116
117    private void initialObjectPush() {
118        mic.pushObject(context);
119    }
120
121    public ChainedModelFilter getPhaseOneFilter() {
122        return phaseOneFilter;
123    }
124
125    public ChainedModelFilter getPhaseTwoFilter() {
126        return phaseTwoFilter;
127    }
128
129
130    protected void analyseDependencies(Model model) {
131
132        List<Supplier<ModelHandlerBase>> analyserSupplierList = modelClassToDependencyAnalyserMap.get(model.getClass());
133
134        if (analyserSupplierList != null) {
135            for (Supplier<ModelHandlerBase> analyserSupplier : analyserSupplierList) {
136                ModelHandlerBase analyser = null;
137
138                if (analyserSupplier != null) {
139                    analyser = analyserSupplier.get();
140                }
141
142                if (analyser != null && !model.isSkipped()) {
143                    callAnalyserHandleOnModel(model, analyser);
144                }
145
146                if (analyser != null && !model.isSkipped()) {
147                    callAnalyserPostHandleOnModel(model, analyser);
148                }
149            }
150        }
151
152        for (Model m : model.getSubModels()) {
153            analyseDependencies(m);
154        }
155
156    }
157
158    private void callAnalyserPostHandleOnModel(Model model, ModelHandlerBase analyser) {
159        try {
160            analyser.postHandle(mic, model);
161        } catch (ModelHandlerException e) {
162            addError("Failed to invoke postHandle on model " + model.getTag(), e);
163        }
164    }
165
166    private void callAnalyserHandleOnModel(Model model, ModelHandlerBase analyser) {
167        try {
168            analyser.handle(mic, model);
169        } catch (ModelHandlerException e) {
170            addError("Failed to traverse model " + model.getTag(), e);
171        }
172    }
173
174    static final int DENIED = -1;
175
176    private ModelHandlerBase createHandler(Model model) {
177        ModelHandlerFactoryMethod modelFactoryMethod = modelClassToHandlerMap.get(model.getClass());
178
179        if (modelFactoryMethod == null) {
180            addError("Can't handle model of type " + model.getClass() + "  with tag: " + model.getTag() + " at line "
181                    + model.getLineNumber());
182            return null;
183        }
184
185        ModelHandlerBase handler = modelFactoryMethod.make(context, mic);
186        if (handler == null)
187            return null;
188        if (!handler.isSupportedModelType(model)) {
189            addWarn("Handler [" + handler.getClass() + "] does not support " + model.idString());
190            return null;
191        }
192        return handler;
193    }
194
195    protected int mainTraverse(Model model, ModelFilter modelFiler) {
196
197        FilterReply filterReply = modelFiler.decide(model);
198        if (filterReply == FilterReply.DENY)
199            return DENIED;
200
201        int count = 0;
202
203        try {
204            ModelHandlerBase handler = null;
205            boolean unhandled = model.isUnhandled();
206
207            if (unhandled) {
208                handler = createHandler(model);
209                if (handler != null) {
210                    handler.handle(mic, model);
211                    model.markAsHandled();
212                    count++;
213                }
214            }
215            // recurse into submodels handled or not
216            if (!model.isSkipped()) {
217                for (Model m : model.getSubModels()) {
218                    count += mainTraverse(m, modelFiler);
219                }
220            }
221
222            if (unhandled && handler != null) {
223                handler.postHandle(mic, model);
224            }
225        } catch (ModelHandlerException e) {
226            addError("Failed to traverse model " + model.getTag(), e);
227        }
228        return count;
229    }
230
231    protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) {
232
233        FilterReply filterReply = modelFilter.decide(model);
234        if (filterReply == FilterReply.DENY) {
235            return 0;
236        }
237
238        int count = 0;
239
240        try {
241
242            boolean allDependenciesStarted = allDependenciesStarted(model);
243
244            ModelHandlerBase handler = null;
245            if (model.isUnhandled() && allDependenciesStarted) {
246                handler = createHandler(model);
247                if (handler != null) {
248                    handler.handle(mic, model);
249                    model.markAsHandled();
250                    count++;
251                }
252            }
253
254            if (!allDependenciesStarted && !dependencyIsADirectSubmodel(model)) {
255                return count;
256            }
257
258            if (!model.isSkipped()) {
259                for (Model m : model.getSubModels()) {
260                    count += secondPhaseTraverse(m, modelFilter);
261                }
262            }
263            if (handler != null) {
264                handler.postHandle(mic, model);
265            }
266        } catch (ModelHandlerException e) {
267            addError("Failed to traverse model " + model.getTag(), e);
268        }
269        return count;
270    }
271
272    private boolean dependencyIsADirectSubmodel(Model model) {
273        List<String> dependecyNames = this.mic.getDependeeNamesForModel(model);
274        if (dependecyNames == null || dependecyNames.isEmpty()) {
275            return false;
276        }
277        for (Model submodel : model.getSubModels()) {
278            if (submodel instanceof NamedComponentModel) {
279                NamedComponentModel namedComponentModel = (NamedComponentModel) submodel;
280                String subModelName = namedComponentModel.getName();
281                if (dependecyNames.contains(subModelName)) {
282                    return true;
283                }
284            }
285        }
286
287        return false;
288    }
289
290    private boolean allDependenciesStarted(Model model) {
291        // assumes that DependencyDefinitions have been registered
292        List<String> dependencyNames = mic.getDependeeNamesForModel(model);
293
294        if (dependencyNames == null || dependencyNames.isEmpty()) {
295            return true;
296        }
297        for (String name : dependencyNames) {
298            boolean isStarted = mic.isNamedDependeeStarted(name);
299            if (isStarted == false) {
300                return false;
301            }
302        }
303        return true;
304    }
305
306}