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.ClassUtil; 048import ch.qos.logback.tyler.base.util.StringToVariableStament; 049import com.squareup.javapoet.ClassName; 050import com.squareup.javapoet.MethodSpec; 051 052import java.lang.reflect.Method; 053 054public class ImplicitModelHandler extends ModelHandlerBase { 055 056 boolean inError = false; 057 private final BeanDescriptionCache beanDescriptionCache; 058 ImplicitModelHandlerData implicitModelHandlerData; 059 AggregationAssessor aggregationAssessor; 060 AggregationType aggregationType; 061 062 static public final String IGNORING_UNKNOWN_PROP = "Ignoring unknown property"; 063 064 public ImplicitModelHandler(Context context, BeanDescriptionCache beanDescriptionCache) { 065 super(context); 066 this.beanDescriptionCache = beanDescriptionCache; 067 } 068 069 static public ImplicitModelHandler makeInstance(Context context, ModelInterpretationContext mic) { 070 BeanDescriptionCache beanDescriptionCache = mic.getBeanDescriptionCache(); 071 return new ImplicitModelHandler(context, beanDescriptionCache); 072 } 073 074 protected Class<? extends ImplicitModel> getSupportedModelClass() { 075 return ImplicitModel.class; 076 } 077 078 @Override 079 public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { 080 ImplicitModel implicitModel = (ImplicitModel) model; 081 082 TylerModelInterpretationContext tmic = (TylerModelInterpretationContext) mic; 083 084 // calling intercon.peekObject with an empty stack will throw an exception 085 if (mic.isObjectStackEmpty()) { 086 inError = true; 087 return; 088 } 089 090 String nestedElementTagName = implicitModel.getTag(); 091 Object o = mic.peekObject(); 092 093 if (o == null) { 094 addError("Null object at the top of the stack"); 095 inError = true; 096 return; 097 } 098 099 if (!(o instanceof ImplicitModelHandlerData)) { 100 addError("Was expecting class of type " + ImplicitModelHandlerData.class.getName() + " but found " 101 + o.getClass().getName()); 102 inError = true; 103 return; 104 } 105 ImplicitModelHandlerData tylerImplicitData = (ImplicitModelHandlerData) o; 106 this.aggregationAssessor = new AggregationAssessor(beanDescriptionCache, 107 tylerImplicitData.getParentObjectClass()); 108 aggregationAssessor.setContext(context); 109 110 this.aggregationType = aggregationAssessor.computeAggregationType(nestedElementTagName); 111 112 switch (aggregationType) { 113 case NOT_FOUND: 114 addWarn(IGNORING_UNKNOWN_PROP + " [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]"); 115 inError = true; 116 // no point in processing submodels 117 implicitModel.markAsSkipped(); 118 return; 119 case AS_BASIC_PROPERTY: 120 case AS_BASIC_PROPERTY_COLLECTION: 121 doBasicProperty(mic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType); 122 return; 123 // we only push action data if NestComponentIA is applicable 124 case AS_COMPLEX_PROPERTY_COLLECTION: 125 case AS_COMPLEX_PROPERTY: 126 doComplex(tmic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType); 127 return; 128 default: 129 addError("PropertySetter.computeAggregationType returned " + aggregationType); 130 inError = true; 131 return; 132 } 133 134 } 135 136 private void doComplex(TylerModelInterpretationContext tmic, ImplicitModel implicitModel, 137 AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple, 138 AggregationType aggregationType) { 139 140 String className = implicitModel.getClassName(); 141 String fqcn = tmic.getImport(className); 142 String nestedElementTagName = implicitModel.getTag(); 143 144 Class<?> componentClass = null; 145 try { 146 147 if (!OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) { 148 componentClass = ClassUtil.restrictecLoadClass(fqcn, context); 149 } else { 150 // guess class name via implicit rules 151 componentClass = aggregationAssessor.getClassNameViaImplicitRules(nestedElementTagName, aggregationType, 152 tmic.getDefaultNestedComponentRegistry()); 153 } 154 155 if (componentClass == null) { 156 inError = true; 157 String errMsg = "Could not find an appropriate class for property [" + nestedElementTagName + "]"; 158 addError(errMsg); 159 return; 160 } 161 if (OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) { 162 addInfo("Assuming default type [" + componentClass.getName() + "] for [" + nestedElementTagName 163 + "] property"); 164 } 165 166 this.implicitModelHandlerData = addJavaStatementForComplexProperty(tmic, implicitModel, 167 classAndMethodSpecTuple, componentClass); 168 tmic.pushObject(implicitModelHandlerData); 169 170 } catch (Exception oops) { 171 inError = true; 172 String msg = "Could not create component [" + implicitModel.getTag() + "] of type [" + fqcn + "]"; 173 addError(msg, oops); 174 } 175 176 } 177 178 private ImplicitModelHandlerData addJavaStatementForComplexProperty(TylerModelInterpretationContext tmic, 179 ImplicitModel implicitModel, ImplicitModelHandlerData implicitModelHandlerData, Class<?> componentClass) { 180 181 MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder; 182 String parentVariableName = implicitModelHandlerData.getVariableName(); 183 String variableName = StringUtil.lowercaseFirstLetter(componentClass.getSimpleName()); 184 ClassName componentCN = ClassName.get(componentClass.getPackageName(), componentClass.getSimpleName()); 185 186 methodSpecBuilder.addCode("\n"); 187 methodSpecBuilder.addComment("Configure component of type $T", componentCN); 188 methodSpecBuilder.addStatement("$1T $2N = new $1T()", componentCN, variableName); 189 190 boolean isContextAware = ClassUtil.classImplements(componentClass, ContextAware.class); 191 192 if(isContextAware) { 193 methodSpecBuilder.addStatement("$N.setContext($N)", variableName, tmic.getContextFieldSpec()); 194 } else { 195 methodSpecBuilder.addComment("$T not ContextAware", componentClass); 196 } 197 198 ImplicitModelHandlerData cvnmsbt = new ImplicitModelHandlerData(parentVariableName, componentClass, 199 variableName, methodSpecBuilder); 200 return cvnmsbt; 201 } 202 203 private void doBasicProperty(ModelInterpretationContext mic, ImplicitModel implicitModel, 204 AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple, 205 AggregationType aggregationType) { 206 String bodyText = implicitModel.getBodyText(); 207 String nestedElementTagName = implicitModel.getTag(); 208 209 switch (aggregationType) { 210 case AS_BASIC_PROPERTY: 211 Method setterMethod = aggregationAssessor.findSetterMethod(nestedElementTagName); 212 Class<?>[] paramTypes = setterMethod.getParameterTypes(); 213 setPropertyJavaStatement(classAndMethodSpecTuple, setterMethod, bodyText, paramTypes[0]); 214 break; 215 case AS_BASIC_PROPERTY_COLLECTION: 216 Method adderMethod = aggregationAssessor.findAdderMethod(nestedElementTagName); 217 Class<?>[] addedParamTypes = adderMethod.getParameterTypes(); 218 setPropertyJavaStatement(classAndMethodSpecTuple, adderMethod, bodyText, addedParamTypes[0]); 219 220 break; 221 default: 222 addError("Unexpected aggregationType " + aggregationType); 223 } 224 } 225 226 private void setPropertyJavaStatement(ImplicitModelHandlerData classAndMethodSpecTuple, Method setterMethod, 227 String value, Class<?> type) { 228 229 MethodSpec.Builder methodSpecBuilder = classAndMethodSpecTuple.methodSpecBuilder; 230 String variableName = classAndMethodSpecTuple.getVariableName(); 231 //String setterSuffix = StringUtil.capitalizeFirstLetter(nestedElementTagName); 232 String valuePart = StringToVariableStament.convertArg(type, value); 233 if (Boolean.TYPE.isAssignableFrom(type)) { 234 value = value.toLowerCase(); 235 } 236 methodSpecBuilder.addStatement("$N.$N(" + valuePart + ")", variableName, setterMethod.getName(), value); 237 238 Integer.parseInt("1"); 239 } 240 241 @Override 242 public void postHandle(ModelInterpretationContext mic, Model model) { 243 if (inError) { 244 return; 245 } 246 if (implicitModelHandlerData != null) { 247 postHandleComplex(mic, model); 248 } 249 250 } 251 252 private void postHandleComplex(ModelInterpretationContext mic, Model model) { 253 254 Object o = mic.peekObject(); 255 if (o != implicitModelHandlerData) { 256 addError("The object on the top the of the stack is not the " + ImplicitModelHandlerData.class 257 + " instance pushed earlier."); 258 } else { 259 mic.popObject(); 260 ImplicitModel implicitModel = (ImplicitModel) model; 261 MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder; 262 String parentVariableName = implicitModelHandlerData.getParentVariableName(); 263 String variableName = implicitModelHandlerData.getVariableName(); 264 Class objClass = implicitModelHandlerData.getParentObjectClass(); 265 266 Method method = switch (aggregationType) { 267 case AS_COMPLEX_PROPERTY -> aggregationAssessor.findSetterMethod(implicitModel.getTag()); 268 case AS_COMPLEX_PROPERTY_COLLECTION -> aggregationAssessor.findAdderMethod(implicitModel.getTag()); 269 default -> throw new IllegalArgumentException("unexpected aggregationType "+aggregationType); 270 }; 271 272 273 AggregationAssessor nestedAggregationAssessor = new AggregationAssessor(beanDescriptionCache, objClass); 274 nestedAggregationAssessor.setContext(context); 275 276 Method parentSetterMethod = nestedAggregationAssessor.findSetterMethod(ModelConstants.PARENT_PROPPERTY_KEY); 277 278 if (parentSetterMethod != null) { 279 methodSpecBuilder.addStatement("$N.$N($N)", variableName, parentSetterMethod.getName(), 280 parentVariableName); 281 } else { 282 methodSpecBuilder.addComment("===========no parent setter"); 283 } 284 285 //methodSpecBuilder.addComment("start the complex property if it implements LifeCycle and is not"); 286 //methodSpecBuilder.addComment("marked with a @NoAutoStart annotation"); 287 288 boolean shouldBeStarted = ClassUtil.shouldBeStarted(objClass); 289 290 if(shouldBeStarted) { 291 methodSpecBuilder.addStatement("$N.start()", variableName); 292 } else { 293 methodSpecBuilder.addComment("$N of class $T does not implement $T interface,", variableName, objClass, LifeCycle.class); 294 methodSpecBuilder.addComment("or alternatively class $T is annotated with @NoAutoStart", objClass); 295 } 296 297 methodSpecBuilder.addComment("Inject component of type $T into parent", objClass); 298 methodSpecBuilder.addStatement("$N.$N($N)", parentVariableName, method.getName(), variableName); 299 } 300 } 301}