001/* 002 * Copyright (c) 2024 QOS.ch Sarl (Switzerland) 003 * All rights reserved. 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining 006 * a copy of this software and associated documentation files (the 007 * "Software"), to deal in the Software without restriction, including 008 * without limitation the rights to use, copy, modify, merge, publish, 009 * distribute, sublicense, and/or sell copies of the Software, and to 010 * permit persons to whom the Software is furnished to do so, subject to 011 * the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be 014 * included in all copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 017 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 018 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 021 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 023 * 024 * 025 * 026 */ 027 028package ch.qos.logback.tyler.base.handler; 029 030import ch.qos.logback.core.Context; 031import ch.qos.logback.core.joran.spi.NoAutoStartUtil; 032import ch.qos.logback.core.joran.util.AggregationAssessor; 033import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache; 034import ch.qos.logback.core.model.ImplicitModel; 035import ch.qos.logback.core.model.Model; 036import ch.qos.logback.core.model.ModelConstants; 037import ch.qos.logback.core.model.processor.ModelHandlerBase; 038import ch.qos.logback.core.model.processor.ModelHandlerException; 039import ch.qos.logback.core.model.processor.ModelInterpretationContext; 040import ch.qos.logback.core.spi.ContextAware; 041import ch.qos.logback.core.spi.LifeCycle; 042import ch.qos.logback.core.util.AggregationType; 043import ch.qos.logback.core.util.Loader; 044import ch.qos.logback.core.util.OptionHelper; 045import ch.qos.logback.core.util.StringUtil; 046import ch.qos.logback.tyler.base.TylerModelInterpretationContext; 047import ch.qos.logback.tyler.base.util.StringToVariableStament; 048import com.squareup.javapoet.ClassName; 049import com.squareup.javapoet.MethodSpec; 050 051import java.lang.reflect.Method; 052 053public class ImplicitModelHandler extends ModelHandlerBase { 054 055 boolean inError = false; 056 private final BeanDescriptionCache beanDescriptionCache; 057 ImplicitModelHandlerData implicitModelHandlerData; 058 AggregationAssessor aggregationAssessor; 059 060 static public final String IGNORING_UNKNOWN_PROP = "Ignoring unknown property"; 061 062 public ImplicitModelHandler(Context context, BeanDescriptionCache beanDescriptionCache) { 063 super(context); 064 this.beanDescriptionCache = beanDescriptionCache; 065 } 066 067 static public ImplicitModelHandler makeInstance(Context context, ModelInterpretationContext mic) { 068 BeanDescriptionCache beanDescriptionCache = mic.getBeanDescriptionCache(); 069 return new ImplicitModelHandler(context, beanDescriptionCache); 070 } 071 072 protected Class<? extends ImplicitModel> getSupportedModelClass() { 073 return ImplicitModel.class; 074 } 075 076 @Override 077 public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { 078 ImplicitModel implicitModel = (ImplicitModel) model; 079 080 TylerModelInterpretationContext tmic = (TylerModelInterpretationContext) mic; 081 082 // calling intercon.peekObject with an empty stack will throw an exception 083 if (mic.isObjectStackEmpty()) { 084 inError = true; 085 return; 086 } 087 088 String nestedElementTagName = implicitModel.getTag(); 089 Object o = mic.peekObject(); 090 091 if (o == null) { 092 addError("Null object at the top of the stack"); 093 inError = true; 094 return; 095 } 096 097 if (!(o instanceof ImplicitModelHandlerData)) { 098 addError("Was expecting class of type " + ImplicitModelHandlerData.class.getName() + " but found" 099 + o.getClass().getName()); 100 inError = true; 101 return; 102 } 103 ImplicitModelHandlerData tylerImplicitData = (ImplicitModelHandlerData) o; 104 this.aggregationAssessor = new AggregationAssessor(beanDescriptionCache, 105 tylerImplicitData.getParentObjectClass()); 106 aggregationAssessor.setContext(context); 107 108 AggregationType aggregationType = aggregationAssessor.computeAggregationType(nestedElementTagName); 109 110 switch (aggregationType) { 111 case NOT_FOUND: 112 addWarn(IGNORING_UNKNOWN_PROP + " [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]"); 113 inError = true; 114 // no point in processing submodels 115 implicitModel.markAsSkipped(); 116 return; 117 case AS_BASIC_PROPERTY: 118 case AS_BASIC_PROPERTY_COLLECTION: 119 doBasicProperty(mic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType); 120 return; 121 // we only push action data if NestComponentIA is applicable 122 case AS_COMPLEX_PROPERTY_COLLECTION: 123 case AS_COMPLEX_PROPERTY: 124 doComplex(tmic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType); 125 return; 126 default: 127 addError("PropertySetter.computeAggregationType returned " + aggregationType); 128 inError = true; 129 return; 130 } 131 132 } 133 134 private void doComplex(TylerModelInterpretationContext tmic, ImplicitModel implicitModel, 135 AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple, 136 AggregationType aggregationType) { 137 138 String className = implicitModel.getClassName(); 139 String fqcn = tmic.getImport(className); 140 String nestedElementTagName = implicitModel.getTag(); 141 142 Class<?> componentClass = null; 143 try { 144 145 if (!OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) { 146 componentClass = Loader.loadClass(fqcn, context); 147 } else { 148 // guess class name via implicit rules 149 componentClass = aggregationAssessor.getClassNameViaImplicitRules(nestedElementTagName, aggregationType, 150 tmic.getDefaultNestedComponentRegistry()); 151 } 152 153 if (componentClass == null) { 154 inError = true; 155 String errMsg = "Could not find an appropriate class for property [" + nestedElementTagName + "]"; 156 addError(errMsg); 157 return; 158 } 159 if (OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) { 160 addInfo("Assuming default type [" + componentClass.getName() + "] for [" + nestedElementTagName 161 + "] property"); 162 } 163 164 this.implicitModelHandlerData = addJavaStatementForComplexProperty(tmic, implicitModel, 165 classAndMethodSpecTuple, componentClass); 166 tmic.pushObject(implicitModelHandlerData); 167 168 } catch (Exception oops) { 169 inError = true; 170 String msg = "Could not create component [" + implicitModel.getTag() + "] of type [" + fqcn + "]"; 171 addError(msg, oops); 172 } 173 174 } 175 176 private ImplicitModelHandlerData addJavaStatementForComplexProperty(TylerModelInterpretationContext tmic, 177 ImplicitModel implicitModel, ImplicitModelHandlerData implicitModelHandlerData, Class<?> componentClass) { 178 179 MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder; 180 String parentVariableName = implicitModelHandlerData.getVariableName(); 181 String variableName = StringUtil.lowercaseFirstLetter(componentClass.getSimpleName()); 182 ClassName componentCN = ClassName.get(componentClass.getPackageName(), componentClass.getSimpleName()); 183 184 methodSpecBuilder.addCode("\n"); 185 methodSpecBuilder.addComment("Configure component of type $T", componentCN); 186 methodSpecBuilder.addStatement("$1T $2N = new $1T()", componentCN, variableName); 187 methodSpecBuilder.beginControlFlow("if ($N instanceof $T)", variableName, ContextAware.class); 188 methodSpecBuilder.addStatement("$N.setContext($N)", variableName, tmic.getContextFieldSpec()); 189 methodSpecBuilder.endControlFlow(); 190 191 ImplicitModelHandlerData cvnmsbt = new ImplicitModelHandlerData(parentVariableName, componentClass, 192 variableName, methodSpecBuilder); 193 return cvnmsbt; 194 } 195 196 private void doBasicProperty(ModelInterpretationContext mic, ImplicitModel implicitModel, 197 AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple, 198 AggregationType aggregationType) { 199 String finalBody = mic.subst(implicitModel.getBodyText()); 200 String nestedElementTagName = implicitModel.getTag(); 201 202 switch (aggregationType) { 203 case AS_BASIC_PROPERTY: 204 Method setterMethod = aggregationAssessor.findSetterMethod(nestedElementTagName); 205 Class<?>[] paramTypes = setterMethod.getParameterTypes(); 206 setPropertyJavaStatement(classAndMethodSpecTuple, setterMethod, finalBody, paramTypes[0]); 207 break; 208 case AS_BASIC_PROPERTY_COLLECTION: 209 //actionData.parentBean.addBasicProperty(actionData.propertyName, finalBody); 210 break; 211 default: 212 addError("Unexpected aggregationType " + aggregationType); 213 } 214 } 215 216 private void setPropertyJavaStatement(ImplicitModelHandlerData classAndMethodSpecTuple, Method setterMethod, 217 String value, Class<?> type) { 218 219 MethodSpec.Builder methodSpecBuilder = classAndMethodSpecTuple.methodSpecBuilder; 220 String variableName = classAndMethodSpecTuple.getVariableName(); 221 //String setterSuffix = StringUtil.capitalizeFirstLetter(nestedElementTagName); 222 String valuePart = StringToVariableStament.convertArg(type); 223 methodSpecBuilder.addStatement("$N.$N(" + valuePart + ")", variableName, setterMethod.getName(), 224 value.toLowerCase()); 225 } 226 227 @Override 228 public void postHandle(ModelInterpretationContext mic, Model model) { 229 if (inError) { 230 return; 231 } 232 if (implicitModelHandlerData != null) { 233 postHandleComplex(mic, model); 234 } 235 236 } 237 238 private void postHandleComplex(ModelInterpretationContext mic, Model model) { 239 240 Object o = mic.peekObject(); 241 if (o != implicitModelHandlerData) { 242 addError("The object on the top the of the stack is not the " + ImplicitModelHandlerData.class 243 + " instance pushed earlier."); 244 } else { 245 mic.popObject(); 246 ImplicitModel implicitModel = (ImplicitModel) model; 247 MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder; 248 String parentVariableName = implicitModelHandlerData.getParentVariableName(); 249 String variableName = implicitModelHandlerData.getVariableName(); 250 Class objClass = implicitModelHandlerData.getParentObjectClass(); 251 Method setterMethod = aggregationAssessor.findSetterMethod(implicitModel.getTag()); 252 253 AggregationAssessor nestedAggregationAssessor = new AggregationAssessor(beanDescriptionCache, 254 setterMethod.getParameterTypes()[0]); 255 nestedAggregationAssessor.setContext(context); 256 257 Method parentSetterMethod = nestedAggregationAssessor.findSetterMethod(ModelConstants.PARENT_PROPPERTY_KEY); 258 259 if (parentSetterMethod != null) { 260 methodSpecBuilder.addStatement("$N.$N($N)", variableName, parentSetterMethod.getName(), 261 parentVariableName); 262 } else { 263 methodSpecBuilder.addComment("===========no parent setter"); 264 } 265 266 methodSpecBuilder.addComment("start the complex property if it implements LifeCycle and is not"); 267 methodSpecBuilder.addComment("marked with a @NoAutoStart annotation"); 268 methodSpecBuilder.beginControlFlow("if($T.shouldBeStarted($N))", NoAutoStartUtil.class, variableName); 269 methodSpecBuilder.addStatement("(($T) $N).start()", LifeCycle.class, variableName); 270 methodSpecBuilder.endControlFlow(); 271 272 methodSpecBuilder.addComment("Inject component of type $T into parent", objClass); 273 methodSpecBuilder.addStatement("$N.$N($N)", parentVariableName, setterMethod.getName(), variableName); 274 } 275 } 276}