001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2022, 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 v1.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.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ülcü
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) {
139            try {
140                analyser.handle(mic, model);
141            } catch (ModelHandlerException e) {
142                addError("Failed to traverse model " + model.getTag(), e);
143            }
144        }
145
146        for (Model m : model.getSubModels()) {
147            analyseDependencies(m);
148        }
149        if (analyser != null) {
150            try {
151                analyser.postHandle(mic, model);
152            } catch (ModelHandlerException e) {
153                addError("Failed to invoke postHandle on model " + model.getTag(), e);
154            }
155        }
156    }
157
158    static final int DENIED = -1;
159
160    private ModelHandlerBase createHandler(Model model) {
161        ModelHandlerFactoryMethod modelFactoryMethod = modelClassToHandlerMap.get(model.getClass());
162
163        if (modelFactoryMethod == null) {
164            addError("Can't handle model of type " + model.getClass() + "  with tag: " + model.getTag() + " at line "
165                    + model.getLineNumber());
166            return null;
167        }
168
169        ModelHandlerBase handler = modelFactoryMethod.make(context, mic);
170        if (handler == null)
171            return null;
172        if (!handler.isSupportedModelType(model)) {
173            addWarn("Handler [" + handler.getClass() + "] does not support " + model.idString());
174            return null;
175        }
176        return handler;
177    }
178
179    protected int mainTraverse(Model model, ModelFilter modelFiler) {
180
181        FilterReply filterReply = modelFiler.decide(model);
182        if (filterReply == FilterReply.DENY)
183            return DENIED;
184
185        int count = 0;
186
187        try {
188            ModelHandlerBase handler = null;
189            boolean unhandled = model.isUnhandled();
190
191            if (unhandled) {
192                handler = createHandler(model);
193                if (handler != null) {
194                    handler.handle(mic, model);
195                    model.markAsHandled();
196                    count++;
197                }
198            }
199            // recurse into submodels handled or not
200            if (!model.isSkipped()) {
201                for (Model m : model.getSubModels()) {
202                    count += mainTraverse(m, modelFiler);
203                }
204            }
205
206            if (unhandled && handler != null) {
207                handler.postHandle(mic, model);
208            }
209        } catch (ModelHandlerException e) {
210            addError("Failed to traverse model " + model.getTag(), e);
211        }
212        return count;
213    }
214
215    protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) {
216
217        FilterReply filterReply = modelFilter.decide(model);
218        if (filterReply == FilterReply.DENY) {
219            return 0;
220        }
221
222        int count = 0;
223
224        try {
225
226            boolean allDependenciesStarted = allDependenciesStarted(model);
227
228            ModelHandlerBase handler = null;
229            if (model.isUnhandled() && allDependenciesStarted) {
230                handler = createHandler(model);
231                if (handler != null) {
232                    handler.handle(mic, model);
233                    model.markAsHandled();
234                    count++;
235                }
236            }
237
238            if (!allDependenciesStarted && !dependencyIsADirectSubmodel(model)) {
239                return count;
240            }
241
242            if (!model.isSkipped()) {
243                for (Model m : model.getSubModels()) {
244                    count += secondPhaseTraverse(m, modelFilter);
245                }
246            }
247            if (handler != null) {
248                handler.postHandle(mic, model);
249            }
250        } catch (ModelHandlerException e) {
251            addError("Failed to traverse model " + model.getTag(), e);
252        }
253        return count;
254    }
255
256    private boolean dependencyIsADirectSubmodel(Model model) {
257        List<String> dependecyNames = this.mic.getDependeeNamesForModel(model);
258        if (dependecyNames == null || dependecyNames.isEmpty()) {
259            return false;
260        }
261        for (Model submodel : model.getSubModels()) {
262            if (submodel instanceof NamedComponentModel) {
263                NamedComponentModel namedComponentModel = (NamedComponentModel) submodel;
264                String subModelName = namedComponentModel.getName();
265                if (dependecyNames.contains(subModelName)) {
266                    return true;
267                }
268            }
269        }
270
271        return false;
272    }
273
274    private boolean allDependenciesStarted(Model model) {
275        List<String> dependencyNames = mic.getDependeeNamesForModel(model);
276       
277        if (dependencyNames == null || dependencyNames.isEmpty()) {
278            return true;
279        }
280        for (String name : dependencyNames) {
281            boolean isStarted = mic.isNamedDependeeStarted(name);
282            if (isStarted == false) {
283                return false;
284            }
285        }
286        return true;
287    }
288
289    ModelHandlerBase instantiateHandler(Class<? extends ModelHandlerBase> handlerClass) {
290        try {
291            Constructor<? extends ModelHandlerBase> commonConstructor = getWithContextConstructor(handlerClass);
292            if (commonConstructor != null) {
293                return commonConstructor.newInstance(context);
294            }
295            Constructor<? extends ModelHandlerBase> constructorWithBDC = getWithContextAndBDCConstructor(handlerClass);
296            if (constructorWithBDC != null) {
297                return constructorWithBDC.newInstance(context, mic.getBeanDescriptionCache());
298            }
299            addError("Failed to find suitable constructor for class [" + handlerClass + "]");
300            return null;
301        } catch (InstantiationException | IllegalAccessException | SecurityException | IllegalArgumentException
302                | InvocationTargetException e1) {
303            addError("Failed to instantiate " + handlerClass);
304            return null;
305        }
306    }
307
308    private Constructor<? extends ModelHandlerBase> getWithContextConstructor(
309            Class<? extends ModelHandlerBase> handlerClass) {
310        try {
311            Constructor<? extends ModelHandlerBase> constructor = handlerClass.getConstructor(Context.class);
312            return constructor;
313        } catch (NoSuchMethodException e) {
314            return null;
315        }
316    }
317
318    private Constructor<? extends ModelHandlerBase> getWithContextAndBDCConstructor(
319            Class<? extends ModelHandlerBase> handlerClass) {
320        try {
321            Constructor<? extends ModelHandlerBase> constructor = handlerClass.getConstructor(Context.class,
322                    BeanDescriptionCache.class);
323            return constructor;
324        } catch (NoSuchMethodException e) {
325            return null;
326        }
327    }
328
329}