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, tylerImplicitData.getParentObjectClass()); 105 aggregationAssessor.setContext(context); 106 107 AggregationType aggregationType = aggregationAssessor.computeAggregationType(nestedElementTagName); 108 109 switch (aggregationType) { 110 case NOT_FOUND: 111 addWarn(IGNORING_UNKNOWN_PROP + " [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]"); 112 inError = true; 113 // no point in processing submodels 114 implicitModel.markAsSkipped(); 115 return; 116 case AS_BASIC_PROPERTY: 117 case AS_BASIC_PROPERTY_COLLECTION: 118 doBasicProperty(mic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType); 119 return; 120 // we only push action data if NestComponentIA is applicable 121 case AS_COMPLEX_PROPERTY_COLLECTION: 122 case AS_COMPLEX_PROPERTY: 123 doComplex(tmic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType); 124 return; 125 default: 126 addError("PropertySetter.computeAggregationType returned " + aggregationType); 127 inError = true; 128 return; 129 } 130 131 } 132 133 private void doComplex(TylerModelInterpretationContext tmic, ImplicitModel implicitModel, 134 AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple, 135 AggregationType aggregationType) { 136 137 String className = implicitModel.getClassName(); 138 String fqcn = tmic.getImport(className); 139 String nestedElementTagName = implicitModel.getTag(); 140 141 Class<?> componentClass = null; 142 try { 143 144 if (!OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) { 145 componentClass = Loader.loadClass(fqcn, context); 146 } else { 147 // guess class name via implicit rules 148 componentClass = aggregationAssessor.getClassNameViaImplicitRules(nestedElementTagName, aggregationType, 149 tmic.getDefaultNestedComponentRegistry()); 150 } 151 152 if (componentClass == null) { 153 inError = true; 154 String errMsg = "Could not find an appropriate class for property [" + nestedElementTagName + "]"; 155 addError(errMsg); 156 return; 157 } 158 if (OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) { 159 addInfo("Assuming default type [" + componentClass.getName() + "] for [" + nestedElementTagName 160 + "] property"); 161 } 162 163 this.implicitModelHandlerData = addJavaStatementForComplexProperty(tmic, implicitModel, 164 classAndMethodSpecTuple, componentClass); 165 tmic.pushObject(implicitModelHandlerData); 166 167 } catch (Exception oops) { 168 inError = true; 169 String msg = "Could not create component [" + implicitModel.getTag() + "] of type [" + fqcn + "]"; 170 addError(msg, oops); 171 } 172 173 } 174 175 private ImplicitModelHandlerData addJavaStatementForComplexProperty(TylerModelInterpretationContext tmic, 176 ImplicitModel implicitModel, ImplicitModelHandlerData implicitModelHandlerData, Class<?> componentClass) { 177 178 MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder; 179 String parentVariableName = implicitModelHandlerData.getVariableName(); 180 String variableName = StringUtil.lowercaseFirstLetter(componentClass.getSimpleName()); 181 ClassName componentCN = ClassName.get(componentClass.getPackageName(), componentClass.getSimpleName()); 182 183 methodSpecBuilder.addCode("\n"); 184 methodSpecBuilder.addComment("Configure component of type $T", componentCN); 185 methodSpecBuilder.addStatement("$1T $2N = new $1T()", componentCN, variableName); 186 methodSpecBuilder.beginControlFlow("if ($N instanceof $T)", variableName, ContextAware.class); 187 methodSpecBuilder.addStatement("$N.setContext($N)", variableName, tmic.getContextFieldSpec()); 188 methodSpecBuilder.endControlFlow(); 189 190 ImplicitModelHandlerData cvnmsbt = new ImplicitModelHandlerData(parentVariableName, componentClass, 191 variableName, methodSpecBuilder); 192 return cvnmsbt; 193 } 194 195 private void doBasicProperty(ModelInterpretationContext mic, ImplicitModel implicitModel, 196 AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple, 197 AggregationType aggregationType) { 198 String finalBody = mic.subst(implicitModel.getBodyText()); 199 String nestedElementTagName = implicitModel.getTag(); 200 201 switch (aggregationType) { 202 case AS_BASIC_PROPERTY: 203 Method setterMethod = aggregationAssessor.findSetterMethod(nestedElementTagName); 204 Class<?>[] paramTypes = setterMethod.getParameterTypes(); 205 setPropertyJavaStatement(classAndMethodSpecTuple, setterMethod, finalBody, paramTypes[0]); 206 break; 207 case AS_BASIC_PROPERTY_COLLECTION: 208 //actionData.parentBean.addBasicProperty(actionData.propertyName, finalBody); 209 break; 210 default: 211 addError("Unexpected aggregationType " + aggregationType); 212 } 213 } 214 215 private void setPropertyJavaStatement(ImplicitModelHandlerData classAndMethodSpecTuple, Method setterMethod, 216 String value, Class<?> type) { 217 218 MethodSpec.Builder methodSpecBuilder = classAndMethodSpecTuple.methodSpecBuilder; 219 String variableName = classAndMethodSpecTuple.getVariableName(); 220 //String setterSuffix = StringUtil.capitalizeFirstLetter(nestedElementTagName); 221 String valuePart = StringToVariableStament.convertArg(type); 222 methodSpecBuilder.addStatement("$N.$N(" + valuePart + ")", variableName, setterMethod.getName(), 223 value.toLowerCase()); 224 } 225 226 @Override 227 public void postHandle(ModelInterpretationContext mic, Model model) { 228 if (inError) { 229 return; 230 } 231 if (implicitModelHandlerData != null) { 232 postHandleComplex(mic, model); 233 } 234 235 } 236 237 private void postHandleComplex(ModelInterpretationContext mic, Model model) { 238 239 Object o = mic.peekObject(); 240 if (o != implicitModelHandlerData) { 241 addError("The object on the top the of the stack is not the " + ImplicitModelHandlerData.class 242 + " instance pushed earlier."); 243 } else { 244 mic.popObject(); 245 ImplicitModel implicitModel = (ImplicitModel) model; 246 MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder; 247 String parentVariableName = implicitModelHandlerData.getParentVariableName(); 248 String variableName = implicitModelHandlerData.getVariableName(); 249 Class objClass = implicitModelHandlerData.getParentObjectClass(); 250 Method setterMethod = aggregationAssessor.findSetterMethod(implicitModel.getTag()); 251 252 AggregationAssessor nestedAggregationAssessor = new AggregationAssessor(beanDescriptionCache, setterMethod.getParameterTypes()[0]); 253 nestedAggregationAssessor.setContext(context); 254 255 Method parentSetterMethod = nestedAggregationAssessor.findSetterMethod(ModelConstants.PARENT_PROPPERTY_KEY); 256 257 if(parentSetterMethod != null) { 258 methodSpecBuilder.addStatement("$N.$N($N)", variableName, parentSetterMethod.getName(),parentVariableName); 259 } else { 260 methodSpecBuilder.addComment("===========no parent setter"); 261 } 262 263 264 265 methodSpecBuilder.addComment("start the complex property if it implements LifeCycle and is not"); 266 methodSpecBuilder.addComment("marked with a @NoAutoStart annotation"); 267 methodSpecBuilder.beginControlFlow("if(($1N instanceof $2T) && $3T.notMarkedWithNoAutoStart($1N))", variableName, LifeCycle.class, 268 NoAutoStartUtil.class); 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}