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 ModelHandlerBase handler = null; 241 if (model.isUnhandled() && allDependenciesStarted) { 242 handler = createHandler(model); 243 if (handler != null) { 244 handler.handle(mic, model); 245 model.markAsHandled(); 246 count++; 247 } 248 } 249 250 if (!allDependenciesStarted && !dependencyIsLocatedInASubmodel(model)) { 251 return count; 252 } 253 254 if (!model.isSkipped()) { 255 for (Model m : model.getSubModels()) { 256 count += secondPhaseTraverse(m, modelFilter); 257 } 258 } 259 if (handler != null) { 260 handler.postHandle(mic, model); 261 } 262 } catch (ModelHandlerException e) { 263 addError("Failed to traverse model " + model.getTag(), e); 264 } 265 return count; 266 } 267 268 private boolean dependencyIsLocatedInASubmodel(Model model) { 269 List<String> dependencyNames = this.mic.getDependencyNamesForModel(model); 270 if (dependencyNames == null || dependencyNames.isEmpty()) { 271 return false; 272 } 273 274 return recursiveIsDependencyPredicate(dependencyNames, model); 275 } 276 277 private boolean recursiveIsDependencyPredicate(List<String> dependencyNames, Model model) { 278 279 if (model instanceof NamedComponentModel) { 280 NamedComponentModel namedComponentModel = (NamedComponentModel) model; 281 String modelName = namedComponentModel.getName(); 282 if (dependencyNames.contains(modelName)) { 283 return true; 284 } 285 } 286 287 for(Model submodel : model.getSubModels()) { 288 boolean result = recursiveIsDependencyPredicate(dependencyNames, submodel); 289 if(result) 290 return true; 291 } 292 293 return false; 294 } 295 296 private boolean allDependenciesStarted(Model model) { 297 // assumes that DependencyDefinitions have been registered 298 List<String> dependencyNames = mic.getDependencyNamesForModel(model); 299 300 if (dependencyNames == null || dependencyNames.isEmpty()) { 301 return true; 302 } 303 for (String name : dependencyNames) { 304 boolean isRegistered = AppenderDeclarationAnalyser.isAppenderDeclared(mic, name); 305 if (!isRegistered) { 306 // non registered dependencies are not taken into account 307 continue; 308 } 309 boolean isStarted = mic.isNamedDependemcyStarted(name); 310 if (!isStarted) { 311 return false; 312 } 313 } 314 return true; 315 } 316 317}