All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vectorprint.configuration.cdi.CDIProperties Maven / Gradle / Ivy

Go to download

A library for settings and parameterization of objects. Key features are support for data types, help for settings and parameters, annotations for ease of use. Settings and parameters both are Clonable and Serializable. More features for settings such as parsing a settingsfile, being observable, readonliness, caching etc. are available. The library contains javacc generated parsers for syntax support for properties, multi valued properties, parameterized objects and multi valued parameters.

The newest version!
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.vectorprint.configuration.cdi;

/*
 * #%L
 * VectorPrintConfig3.0
 * %%
 * Copyright (C) 2011 - 2013 VectorPrint
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
import com.vectorprint.StringConverter;
import com.vectorprint.configuration.EnhancedMap;
import com.vectorprint.configuration.binding.BindingHelper;
import com.vectorprint.configuration.binding.settings.SettingsBindingService;
import com.vectorprint.configuration.decoration.AbstractPropertiesDecorator;
import com.vectorprint.configuration.decoration.visiting.CacheClearingVisitor;
import com.vectorprint.configuration.decoration.visiting.ObservableVisitor;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.spi.Annotated;
import jakarta.enterprise.inject.spi.AnnotatedCallable;
import jakarta.enterprise.inject.spi.AnnotatedField;
import jakarta.enterprise.inject.spi.AnnotatedMethod;
import jakarta.enterprise.inject.spi.AnnotatedParameter;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

/**
 * A CDI Producer of properties allowing you to use @Inject in combination with
 * {@link Property} on fields or methods with one parameter. Injected
 * {@link Property properties} will be updated when property file changes,
 * {@link AutoReload} is true, {@link Property#updatable() } is true and the
 * property is injected in a managed bean.
 *
 * @author Eduard Drenth at VectorPrint.nl
 * @see PropertyResolver
 */
@ApplicationScoped
public class CDIProperties extends AbstractPropertiesDecorator implements PropertyChangeListener {

    private static final BindingHelper bindingHelper = SettingsBindingService.getInstance().getFactory().getBindingHelper();

    @Inject
    public CDIProperties(@PropertyProducer EnhancedMap settings) {
        super(settings);
        ((AbstractPropertiesDecorator) settings).accept(new ObservableVisitor(this));
    }

    private final Map> injectionPoints
            = new HashMap>(100) {
        @Override
        public List get(Object key) {
            if (!containsKey(key)) {
                put((String) key, new ArrayList<>(3));
            }
            return super.get(key);
        }
    };

    private boolean isUpdatable(InjectionPoint ip) {
        Bean bean = ip.getBean();
        boolean rv = bean != null;
        if (rv) {
            Class scope = bean.getScope();
            rv = scope.equals(Singleton.class)
                    || (scope.equals(ApplicationScoped.class) && ip.getMember() instanceof Method);
            if (!rv) {
                log.warn("reloading not supported for %s in %s %s".formatted(List.of(names(ip)), scope.getSimpleName(), ip.getBean().getBeanClass()));
            }

        } else {
                log.warn("reloading not supported for %s".formatted(ip.getMember().getDeclaringClass().getName()+"#"+names(ip)));
        }
        return rv;
    }

    private String[] getKeys(final InjectionPoint ip) {
        final Property property = fromIp(ip);
        String[] rv = names(ip, property);
        if (property.updatable() && isUpdatable(ip)) {
            Arrays.stream(rv).forEach(a -> injectionPoints.get(a).add(ip));
        }
        // TODO here we could log/register where in the application properties are injected
        return rv;
    }

    public static String[] names(InjectionPoint ip) {
        return names(ip, ip.getAnnotated().getAnnotation(Property.class));
    }

    public static String[] names(InjectionPoint ip, Property property) {
        return property.keys().length > 0 ? property.keys() : new String[]{ip.getMember().getName()};
    }

    public static Property fromIp(InjectionPoint ip) {
        final Member member = ip.getMember();
        return member instanceof Method m
                ? m.getAnnotation(Property.class)
                : ip.getAnnotated().getAnnotation(Property.class);
    }

    private Object getDefault(final InjectionPoint ip) {
        Class clazz = (Class) ip.getAnnotated().getBaseType();
        String[] defaultValue = fromIp(ip).defaultValue();
        if (defaultValue.length > 0) {
            return clazz.isArray()
                    ? bindingHelper.convert(defaultValue, clazz)
                    : bindingHelper.convert(defaultValue[0], clazz);
        } else if (boolean.class.equals(clazz) || Boolean.class.equals(clazz)) {
            return false;
        } else if (char.class.equals(clazz) || Character.class.equals(clazz)) {
            return '\u0000';
        } else if (double.class.equals(clazz) || Double.class.equals(clazz)) {
            return 0.0d;
        } else if (float.class.equals(clazz) || Float.class.equals(clazz)) {
            return 0.0f;
        } else if (int.class.equals(clazz) || Integer.class.equals(clazz)) {
            return 0;
        } else if (short.class.equals(clazz) || Short.class.equals(clazz)) {
            return 0;
        } else if (byte.class.equals(clazz) || Byte.class.equals(clazz)) {
            return 0;
        } else if (long.class.equals(clazz) || Long.class.equals(clazz)) {
            return 0;
        } else {
            return null;
        }
    }

    @Produces
    @Properties
    public EnhancedMap getEnhancedMap(InjectionPoint ip) {
        return this;
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public File[] getFileProperties(InjectionPoint ip) {
        return getFileProperties((File[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public File getFileProperty(InjectionPoint ip) {
        return getFileProperty((File) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public Pattern[] getRegexProperties(InjectionPoint ip) {
        return getRegexProperties((Pattern[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public Pattern getRegexProperty(InjectionPoint ip) {
        return getRegexProperty((Pattern) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public Class[] getClassProperties(InjectionPoint ip) throws ClassNotFoundException {
        return getClassProperties((Class[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public Class getClassProperty(InjectionPoint ip) throws ClassNotFoundException {
        return getClassProperty((Class) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public LocalDateTime[] getDateProperties(InjectionPoint ip) {
        return getLocalDateTimeProperties((LocalDateTime[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public LocalDateTime getDateProperty(InjectionPoint ip) {
        return getLocalDateTimeProperty((LocalDateTime) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public byte[] getByteProperties(InjectionPoint ip) {
        return getByteProperties((byte[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public char[] getCharProperties(InjectionPoint ip) {
        return getCharProperties((char[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public short[] getShortProperties(InjectionPoint ip) {
        return getShortProperties((short[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public byte getByteProperty(InjectionPoint ip) {
        return getByteProperty((Byte) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public char getCharProperty(InjectionPoint ip) {
        return getCharProperty((Character) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public short getShortProperty(InjectionPoint ip) {
        return getShortProperty((Short) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public URL[] getURLProperties(InjectionPoint ip) throws MalformedURLException {
        return getURLProperties((URL[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public URL getURLProperty(InjectionPoint ip) throws MalformedURLException {
        return getURLProperty((URL) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public Color[] getColorProperties(InjectionPoint ip) {
        return getColorProperties((Color[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public boolean[] getBooleanProperties(InjectionPoint ip) {
        return getBooleanProperties((boolean[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public long[] getLongProperties(InjectionPoint ip) {
        return getLongProperties((long[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public int[] getIntegerProperties(InjectionPoint ip) {
        return getIntegerProperties((int[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public double[] getDoubleProperties(InjectionPoint ip) {
        return getDoubleProperties((double[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public float[] getFloatProperties(InjectionPoint ip) {
        return getFloatProperties((float[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public String[] getStringProperties(InjectionPoint ip) {
        return getStringProperties((String[]) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public String getProperty(InjectionPoint ip) {
        return getProperty((String) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public long getLongProperty(InjectionPoint ip) {
        return getLongProperty((Long) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public int getIntegerProperty(InjectionPoint ip) {
        return getIntegerProperty((Integer) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public float getFloatProperty(InjectionPoint ip) {
        return getFloatProperty((Float) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public double getDoubleProperty(InjectionPoint ip) {
        return getDoubleProperty((Double) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public Color getColorProperty(InjectionPoint ip) {
        return getColorProperty((Color) getDefault(ip), getKeys(ip));
    }

    @Produces
    @Property
    @Default
    @CheckInjection
    public boolean getBooleanProperty(InjectionPoint ip) {
        return getBooleanProperty((Boolean) getDefault(ip), getKeys(ip));
    }

    @Override
    public EnhancedMap clone() throws CloneNotSupportedException {
        return super.clone();
    }

    private static final CacheClearingVisitor CACHE_CLEARING_VISITOR
            = new CacheClearingVisitor();

    @Override
    public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
        /*
        via the changes and injection points we should be able to set
        new values
         */

        String c = propertyChangeEvent.getPropertyName();
        injectionPoints.get(c).forEach(ip -> {
            // if bean is null issue a warning, injectionpoint is not in a bean (i.e. webservlet)
            Class bc = ip.getMember().getDeclaringClass();
            Object reference = CDI.current().select(bc).get();
            if (reference == null) {
                log.warn(String.format("Bean for %s not present, BeanManager cannot resolve Object holding %s", bc.getName(), List.of(names(ip))));
            } else {
                update(ip, reference, (String[]) propertyChangeEvent.getNewValue());
            }
        });
        accept(CACHE_CLEARING_VISITOR);
    }

    private void update(InjectionPoint ip, Object reference, String... strValue) {
        Annotated annotated = ip.getAnnotated();
        Class clazz = (Class) annotated.getBaseType();
        Object value = clazz.isAssignableFrom(String[].class) ? strValue
                : clazz.isAssignableFrom(String.class) && strValue != null && strValue.length == 1 ? strValue[0] : null;
        if (value == null && strValue != null) {
            StringConverter stringConverter = StringConverter.forClass(clazz);
            if (clazz.isArray() && strValue.length > 0) {
                Object ar = Array.newInstance(clazz, strValue.length);
                IntStream.range(0, strValue.length).forEach(i -> Array.set(ar, i, stringConverter.convert(strValue[i])));
            } else {
                value = stringConverter.convert(strValue[0]);
            }
        }
        if (annotated instanceof AnnotatedField af) {
            Field f = af.getJavaMember();
            try {
                boolean ac = f.canAccess(reference);
                f.setAccessible(true);
                f.set(reference, value);
                f.setAccessible(ac);
            } catch (IllegalAccessException e) {
                log.error(String.format("error updating %s with %s", f, value));
            }
        } else {
            AnnotatedParameter ap = (AnnotatedParameter) annotated;
            AnnotatedCallable declaringCallable = ap.getDeclaringCallable();
            if (declaringCallable instanceof AnnotatedMethod am) {
                Method method = am.getJavaMember();
                if (method.getParameterCount() == 1) {
                    try {
                        boolean ac = method.canAccess(reference);
                        method.setAccessible(true);
                        method.invoke(reference, value);
                        method.setAccessible(ac);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        log.error(String.format("error calling %s with %s", method, value), e);
                    }
                } else {
                    log.warn(String.format("%s has more than one argument, not supported yet", method));
                }
            } else {
                log.warn(String.format("calling constructor %s not supported", declaringCallable));
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy