1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.model.processor;
15  
16  import ch.qos.logback.core.Context;
17  import ch.qos.logback.core.joran.action.ImcplicitActionDataForBasicProperty;
18  import ch.qos.logback.core.joran.action.ImplicitModelData;
19  import ch.qos.logback.core.joran.action.ImplicitModelDataForComplexProperty;
20  import ch.qos.logback.core.joran.spi.NoAutoStartUtil;
21  import ch.qos.logback.core.joran.util.PropertySetter;
22  import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
23  import ch.qos.logback.core.model.ComponentModel;
24  import ch.qos.logback.core.model.ImplicitModel;
25  import ch.qos.logback.core.model.Model;
26  import ch.qos.logback.core.model.ModelConstants;
27  import ch.qos.logback.core.spi.ContextAware;
28  import ch.qos.logback.core.spi.LifeCycle;
29  import ch.qos.logback.core.util.AggregationType;
30  import ch.qos.logback.core.util.Loader;
31  import ch.qos.logback.core.util.OptionHelper;
32  
33  public class ImplicitModelHandler extends ModelHandlerBase {
34  
35      private final BeanDescriptionCache beanDescriptionCache;
36      private ImplicitModelData implicitModelData;
37  
38      static public final String IGNORING_UNKNOWN_PROP = "Ignoring unknown property";
39  
40      boolean inError = false;
41  
42      public ImplicitModelHandler(Context context, BeanDescriptionCache beanDescriptionCache) {
43          super(context);
44          this.beanDescriptionCache = beanDescriptionCache;
45      }
46  
47      protected Class<? extends ImplicitModel> getSupportedModelClass() {
48          return ImplicitModel.class;
49      }
50  
51      static public ImplicitModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
52          BeanDescriptionCache beanDescriptionCache = mic.getBeanDescriptionCache();
53          return new ImplicitModelHandler(context, beanDescriptionCache);
54      }
55  
56      @Override
57      public void handle(ModelInterpretationContext mic, Model model) {
58  
59          ImplicitModel implicitModel = (ImplicitModel) model;
60  
61          // calling intercon.peekObject with an empty stack will throw an exception
62          if (mic.isObjectStackEmpty()) {
63              inError = true;
64              return;
65          }
66          String nestedElementTagName = implicitModel.getTag();
67  
68          Object o = mic.peekObject();
69          PropertySetter parentBean = new PropertySetter(beanDescriptionCache, o);
70          parentBean.setContext(context);
71  
72          AggregationType aggregationType = parentBean.computeAggregationType(nestedElementTagName);
73  
74          switch (aggregationType) {
75          case NOT_FOUND:
76              addWarn(IGNORING_UNKNOWN_PROP+" [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]");
77              this.inError = true;
78              // no point in processing submodels
79              implicitModel.markAsSkipped();
80              return;
81          case AS_BASIC_PROPERTY:
82          case AS_BASIC_PROPERTY_COLLECTION:
83              ImcplicitActionDataForBasicProperty adBasicProperty = new ImcplicitActionDataForBasicProperty(parentBean,
84                      aggregationType, nestedElementTagName);
85              this.implicitModelData = adBasicProperty;
86              doBasicProperty(mic, implicitModel, adBasicProperty);
87              return;
88          // we only push action data if NestComponentIA is applicable
89          case AS_COMPLEX_PROPERTY_COLLECTION:
90          case AS_COMPLEX_PROPERTY:
91              Class<?> propertyType = parentBean.getTypeForComplexProperty(nestedElementTagName, aggregationType);
92              ImplicitModelDataForComplexProperty imdForComplexProperty = new ImplicitModelDataForComplexProperty(parentBean,
93                      aggregationType, nestedElementTagName);
94              imdForComplexProperty.setExpectedPropertyType(propertyType);
95              this.implicitModelData = imdForComplexProperty;
96              doComplex(mic, implicitModel, imdForComplexProperty);
97              return;
98          default:
99              addError("PropertySetter.computeAggregationType returned " + aggregationType);
100             return;
101         }
102 
103     }
104 
105     void doBasicProperty(ModelInterpretationContext interpretationContext, Model model,
106             ImcplicitActionDataForBasicProperty actionData) {
107         String finalBody = interpretationContext.subst(model.getBodyText());
108         // get the action data object pushed in isApplicable() method call
109         // IADataForBasicProperty actionData = (IADataForBasicProperty)
110         // actionDataStack.peek();
111         switch (actionData.aggregationType) {
112         case AS_BASIC_PROPERTY:
113             actionData.parentBean.setProperty(actionData.propertyName, finalBody);
114             break;
115         case AS_BASIC_PROPERTY_COLLECTION:
116             actionData.parentBean.addBasicProperty(actionData.propertyName, finalBody);
117             break;
118         default:
119             addError("Unexpected aggregationType " + actionData.aggregationType);
120         }
121     }
122 
123     public void doComplex(ModelInterpretationContext interpretationContext, ComponentModel componentModel,
124             ImplicitModelDataForComplexProperty imdForComplexProperty) {
125 
126         String propertyClassName = componentModel.getClassName();
127         // perform variable name substitution
128         String substPropertyClassName = interpretationContext.subst(propertyClassName);
129 
130         String fqcn = interpretationContext.getImport(substPropertyClassName);
131 
132         Class<?> propertyClass = null;
133         try {
134 
135             if (!OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) {
136                 propertyClass = Loader.loadClass(fqcn, context);
137             } else {
138                 // guess class name via implicit rules
139                 PropertySetter parentBean = imdForComplexProperty.parentBean;
140                 propertyClass = parentBean.getClassNameViaImplicitRules(imdForComplexProperty.propertyName,
141                         imdForComplexProperty.getAggregationType(), interpretationContext.getDefaultNestedComponentRegistry());
142             }
143 
144             if (propertyClass == null) {
145                 imdForComplexProperty.inError = true;
146                 String errMsg = "Could not find an appropriate class for property [" + componentModel.getTag() + "]";
147                 addError(errMsg);
148                 return;
149             }
150 
151             if (OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) {
152                 addInfo("Assuming default type [" + propertyClass.getName() + "] for [" + componentModel.getTag()
153                         + "] property");
154             }
155 
156 
157 
158             Class<?> expectedPropertyType = imdForComplexProperty.getExpectedPropertyType();
159 
160             Object object = OptionHelper.instantiateClassWithSuperclassRestriction(propertyClass, expectedPropertyType);
161 
162             imdForComplexProperty.setNestedComplexProperty(object);
163 
164             // pass along the context
165             if (imdForComplexProperty.getNestedComplexProperty() instanceof ContextAware) {
166                 ((ContextAware) imdForComplexProperty.getNestedComplexProperty()).setContext(this.context);
167             }
168             // addInfo("Pushing component [" + localName
169             // + "] on top of the object stack.");
170             interpretationContext.pushObject(imdForComplexProperty.getNestedComplexProperty());
171 
172         } catch (Exception oops) {
173             imdForComplexProperty.inError = true;
174             String msg = "Could not create component [" + componentModel.getTag() + "] of type [" + fqcn + "]";
175             addError(msg, oops);
176         }
177     }
178 
179     @Override
180     public void postHandle(ModelInterpretationContext intercon, Model model) {
181         if (inError) {
182             return;
183         }
184 
185         if(implicitModelData == null)
186             return;
187         
188         // the action data can in an incorrect state, in which case we need to 
189         // disengage
190         if(implicitModelData.inError) {
191             return;
192         }
193         if (implicitModelData instanceof ImplicitModelDataForComplexProperty) {
194             postHandleComplex(intercon, model, (ImplicitModelDataForComplexProperty) implicitModelData);
195         }
196 
197     }
198 
199     private void postHandleComplex(ModelInterpretationContext mic, Model model,
200             ImplicitModelDataForComplexProperty imdComplex) {
201 
202         PropertySetter nestedBean = new PropertySetter(beanDescriptionCache,
203                 imdComplex.getNestedComplexProperty());
204         nestedBean.setContext(context);
205 
206         // have the nested element point to its parent if possible
207         if (nestedBean.computeAggregationType(ModelConstants.PARENT_PROPPERTY_KEY) == AggregationType.AS_COMPLEX_PROPERTY) {
208             nestedBean.setComplexProperty(ModelConstants.PARENT_PROPPERTY_KEY, imdComplex.parentBean.getObj());
209         }
210 
211         // start the nested complex property if it implements LifeCycle and is not
212         // marked with a @NoAutoStart annotation
213         Object nestedComplexProperty = imdComplex.getNestedComplexProperty();
214         if (NoAutoStartUtil.shouldBeStarted(nestedComplexProperty)) {
215             ((LifeCycle) nestedComplexProperty).start();
216         }
217 
218         Object o = mic.peekObject();
219 
220         if (o != imdComplex.getNestedComplexProperty()) {
221             addError("The object on the top the of the stack is not the component pushed earlier.");
222         } else {
223             mic.popObject();
224             // Now let us attach the component
225             switch (imdComplex.aggregationType) {
226             case AS_COMPLEX_PROPERTY:
227                 imdComplex.parentBean.setComplexProperty(model.getTag(), imdComplex.getNestedComplexProperty());
228 
229                 break;
230             case AS_COMPLEX_PROPERTY_COLLECTION:
231                 imdComplex.parentBean.addComplexProperty(model.getTag(), imdComplex.getNestedComplexProperty());
232                 break;
233             default:
234                 addError("Unexpected aggregationType " + imdComplex.aggregationType);
235             }
236         }
237     }
238 
239 }