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