PropertyModelHandlerHelper.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */

package ch.qos.logback.core.model.util;

import ch.qos.logback.core.joran.action.ActionUtil;
import ch.qos.logback.core.model.ModelConstants;
import ch.qos.logback.core.model.PropertyModel;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
import ch.qos.logback.core.spi.PropertyContainer;
import ch.qos.logback.core.util.ContextUtil;
import ch.qos.logback.core.util.Loader;
import ch.qos.logback.core.util.OptionHelper;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;

/**
 * Given a {@link PropertyModel} offers methods to inject properties into a {@link PropertyContainer}.
 *
 * @since 1.5.1
 */
public class PropertyModelHandlerHelper extends ContextAwareBase {

    public static final String HANDLE_PROPERTY_MODEL_METHOD_NAME = "handlePropertyModel";

    public PropertyModelHandlerHelper(ContextAware declaredOrigin) {
        super(declaredOrigin);
    }

    /**
     * Given a {@link PropertyModel} inject relevant properties into the given {@link ContextAwarePropertyContainer}
     * parameter.
     *
     * @param capcm
     * @param nameStr
     * @param valueStr
     * @param fileStr
     * @param resourceStr
     * @param scopeStr
     *
     */
    public void handlePropertyModel(ContextAwarePropertyContainer capcm, String nameStr, String valueStr,
            String fileStr, String resourceStr, String scopeStr) {
        PropertyModel propertyModel = new PropertyModel();
        propertyModel.setName(nameStr);
        propertyModel.setValue(valueStr);
        propertyModel.setFile(fileStr);
        propertyModel.setResource(resourceStr);

        propertyModel.setScopeStr(scopeStr);

        handlePropertyModel(capcm, propertyModel);
    }

    /**
     * Given a {@link PropertyModel} inject relevant properties into the given {@link ContextAwarePropertyContainer}
     * parameter.
     *
     * @param capc
     * @param propertyModel
     */
    public void handlePropertyModel(ContextAwarePropertyContainer capc, PropertyModel propertyModel) {
        ActionUtil.Scope scope = ActionUtil.stringToScope(propertyModel.getScopeStr());

        if (checkFileAttributeSanity(propertyModel)) {
            String file = propertyModel.getFile();
            file = capc.subst(file);
            try (FileInputStream istream = new FileInputStream(file)) {
                PropertyModelHandlerHelper.loadAndSetProperties(capc, istream, scope);
            } catch (FileNotFoundException e) {
                addError("Could not find properties file [" + file + "].");
            } catch (IOException | IllegalArgumentException e1) { // IllegalArgumentException is thrown in case the file
                // is badly malformed, i.e a binary.
                addError("Could not read properties file [" + file + "].", e1);
            }
        } else if (checkResourceAttributeSanity(propertyModel)) {
            String resource = propertyModel.getResource();
            resource = capc.subst(resource);
            URL resourceURL = Loader.getResourceBySelfClassLoader(resource);
            if (resourceURL == null) {
                addError("Could not find resource [" + resource + "].");
            } else {
                try (InputStream istream = resourceURL.openStream();) {
                    PropertyModelHandlerHelper.loadAndSetProperties(capc, istream, scope);
                } catch (IOException e) {
                    addError("Could not read resource file [" + resource + "].", e);
                }
            }
        } else if (checkValueNameAttributesSanity(propertyModel)) {
            // earlier versions performed Java '\' escapes for '\\' '\t' etc. Howevver, there is no
            // need to do this. See RegularEscapeUtil.__UNUSED__basicEscape
            String value = propertyModel.getValue();

            // now remove both leading and trailing spaces
            value = value.trim();
            value = capc.subst(value);
            ActionUtil.setProperty(capc, propertyModel.getName(), value, scope);

        } else {
            addError(ModelConstants.INVALID_ATTRIBUTES);
        }
    }

    public static boolean checkFileAttributeSanity(PropertyModel propertyModel) {
        String file = propertyModel.getFile();
        String name = propertyModel.getName();
        String value = propertyModel.getValue();
        String resource = propertyModel.getResource();

        return !(OptionHelper.isNullOrEmptyOrAllSpaces(file)) && (OptionHelper.isNullOrEmptyOrAllSpaces(name)
                && OptionHelper.isNullOrEmptyOrAllSpaces(value) && OptionHelper.isNullOrEmptyOrAllSpaces(resource));
    }

    public static boolean checkResourceAttributeSanity(PropertyModel propertyModel) {
        String file = propertyModel.getFile();
        String name = propertyModel.getName();
        String value = propertyModel.getValue();
        String resource = propertyModel.getResource();

        return !(OptionHelper.isNullOrEmptyOrAllSpaces(resource)) && (OptionHelper.isNullOrEmptyOrAllSpaces(name)
                && OptionHelper.isNullOrEmptyOrAllSpaces(value) && OptionHelper.isNullOrEmptyOrAllSpaces(file));
    }

    public static boolean checkValueNameAttributesSanity(PropertyModel propertyModel) {
        String file = propertyModel.getFile();
        String name = propertyModel.getName();
        String value = propertyModel.getValue();
        String resource = propertyModel.getResource();
        return (!(OptionHelper.isNullOrEmptyOrAllSpaces(name) || OptionHelper.isNullOrEmptyOrAllSpaces(value)) && (
                OptionHelper.isNullOrEmptyOrAllSpaces(file) && OptionHelper.isNullOrEmptyOrAllSpaces(resource)));
    }

    /**
     * Add all the properties found in the argument named 'props' to an InterpretationContext.
     */
    static public void setProperty(ContextAwarePropertyContainer capc, String key, String value,
            ActionUtil.Scope scope) {
        switch (scope) {
        case LOCAL:
            capc.addSubstitutionProperty(key, value);
            break;
        case CONTEXT:
            capc.getContext().putProperty(key, value);
            break;
        case SYSTEM:
            OptionHelper.setSystemProperty(capc, key, value);
        }
    }

    /**
     * Add all the properties found in the argument named 'props' to an InterpretationContext.
     */
    static public void setProperties(ContextAwarePropertyContainer capc, Properties props, ActionUtil.Scope scope) {
        switch (scope) {
        case LOCAL:
            capc.addSubstitutionProperties(props);
            break;
        case CONTEXT:
            ContextUtil cu = new ContextUtil(capc.getContext());
            cu.addProperties(props);
            break;
        case SYSTEM:
            OptionHelper.setSystemProperties(capc, props);
        }
    }

    static public void loadAndSetProperties(ContextAwarePropertyContainer capc, InputStream istream,
            ActionUtil.Scope scope) throws IOException {
        Properties props = new Properties();
        props.load(istream);
        PropertyModelHandlerHelper.setProperties(capc, props, scope);
    }
}