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 ch.qos.logback.core.Context;
017import ch.qos.logback.core.joran.action.ImcplicitActionDataForBasicProperty;
018import ch.qos.logback.core.joran.action.ImplicitModelData;
019import ch.qos.logback.core.joran.action.ImplicitModelDataForComplexProperty;
020import ch.qos.logback.core.joran.spi.NoAutoStartUtil;
021import ch.qos.logback.core.joran.util.PropertySetter;
022import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
023import ch.qos.logback.core.model.ComponentModel;
024import ch.qos.logback.core.model.ImplicitModel;
025import ch.qos.logback.core.model.Model;
026import ch.qos.logback.core.model.ModelConstants;
027import ch.qos.logback.core.spi.ContextAware;
028import ch.qos.logback.core.spi.LifeCycle;
029import ch.qos.logback.core.util.AggregationType;
030import ch.qos.logback.core.util.Loader;
031import ch.qos.logback.core.util.OptionHelper;
032
033public class ImplicitModelHandler extends ModelHandlerBase {
034
035    private final BeanDescriptionCache beanDescriptionCache;
036    private ImplicitModelData implicitModelData;
037
038    static public final String IGNORING_UNKNOWN_PROP = "Ignoring unknown property";
039
040    boolean inError = false;
041
042    public ImplicitModelHandler(Context context, BeanDescriptionCache beanDescriptionCache) {
043        super(context);
044        this.beanDescriptionCache = beanDescriptionCache;
045    }
046
047    protected Class<? extends ImplicitModel> getSupportedModelClass() {
048        return ImplicitModel.class;
049    }
050
051    static public ImplicitModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
052        BeanDescriptionCache beanDescriptionCache = mic.getBeanDescriptionCache();
053        return new ImplicitModelHandler(context, beanDescriptionCache);
054    }
055
056    @Override
057    public void handle(ModelInterpretationContext mic, Model model) {
058
059        ImplicitModel implicitModel = (ImplicitModel) model;
060
061        // calling intercon.peekObject with an empty stack will throw an exception
062        if (mic.isObjectStackEmpty()) {
063            inError = true;
064            return;
065        }
066        String nestedElementTagName = implicitModel.getTag();
067
068        Object o = mic.peekObject();
069        PropertySetter parentBean = new PropertySetter(beanDescriptionCache, o);
070        parentBean.setContext(context);
071
072        AggregationType aggregationType = parentBean.computeAggregationType(nestedElementTagName);
073
074        switch (aggregationType) {
075        case NOT_FOUND:
076            addWarn(IGNORING_UNKNOWN_PROP+" [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]");
077            this.inError = true;
078            // no point in processing submodels
079            implicitModel.markAsSkipped();
080            return;
081        case AS_BASIC_PROPERTY:
082        case AS_BASIC_PROPERTY_COLLECTION:
083            ImcplicitActionDataForBasicProperty adBasicProperty = new ImcplicitActionDataForBasicProperty(parentBean,
084                    aggregationType, nestedElementTagName);
085            this.implicitModelData = adBasicProperty;
086            doBasicProperty(mic, implicitModel, adBasicProperty);
087            return;
088        // we only push action data if NestComponentIA is applicable
089        case AS_COMPLEX_PROPERTY_COLLECTION:
090        case AS_COMPLEX_PROPERTY:
091            Class<?> propertyType = parentBean.getTypeForComplexProperty(nestedElementTagName, aggregationType);
092            ImplicitModelDataForComplexProperty imdForComplexProperty = new ImplicitModelDataForComplexProperty(parentBean,
093                    aggregationType, nestedElementTagName);
094            imdForComplexProperty.setExpectedPropertyType(propertyType);
095            this.implicitModelData = imdForComplexProperty;
096            doComplex(mic, implicitModel, imdForComplexProperty);
097            return;
098        default:
099            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}