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}