org.ldaptive.beans.spring.SpelAttributeValueMutator.java Source code

Java tutorial

Introduction

Here is the source code for org.ldaptive.beans.spring.SpelAttributeValueMutator.java

Source

/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.beans.spring;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.ldaptive.SortBehavior;
import org.ldaptive.beans.Attribute;
import org.ldaptive.beans.AttributeValueMutator;
import org.ldaptive.io.ValueTranscoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

/**
 * Attribute mutator that uses a SPEL expression and evaluation context.
 *
 * @author  Middleware Services
 */
public class SpelAttributeValueMutator implements AttributeValueMutator {

    /** Logger for this class. */
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /** Attribute containing the SPEL expression. */
    private final Attribute attribute;

    /** SPEL expression to access values. */
    private final Expression expression;

    /** Evaluation context. */
    private final EvaluationContext evaluationContext;

    /** Custom transcoder for this attribute. */
    private final ValueTranscoder transcoder;

    /**
     * Creates a new spel attribute value mutator.
     *
     * @param  attr  containing the SPEL configuration
     * @param  context  containing the values
     */
    public SpelAttributeValueMutator(final Attribute attr, final EvaluationContext context) {
        attribute = attr;

        final ExpressionParser parser = new SpelExpressionParser();
        expression = parser
                .parseExpression(attribute.property().length() > 0 ? attribute.property() : attribute.name());
        evaluationContext = context;
        if ("".equals(attribute.transcoder())) {
            transcoder = null;
        } else {
            transcoder = parser.parseExpression(attribute.transcoder()).getValue(ValueTranscoder.class);
        }
    }

    @Override
    public String getName() {
        return attribute.name();
    }

    @Override
    public boolean isBinary() {
        return attribute.binary();
    }

    @Override
    public SortBehavior getSortBehavior() {
        return attribute.sortBehavior();
    }

    @Override
    public Collection<String> getStringValues(final Object object) {
        return getValues(object, String.class);
    }

    @Override
    public Collection<byte[]> getBinaryValues(final Object object) {
        return getValues(object, byte[].class);
    }

    /**
     * Uses the configured expression and evaluation context to retrieve values
     * from the supplied object. Values are the placed in a collection and
     * returned.
     *
     * @param  <T>  either String or byte[]
     * @param  object  to get values from
     * @param  type  of objects to place in the collection
     *
     * @return  values in the supplied object
     */
    protected <T> Collection<T> getValues(final Object object, final Class<T> type) {
        Collection<T> values = null;
        final Object converted = expression.getValue(evaluationContext, object);
        if (converted != null) {
            if (converted instanceof byte[] || converted instanceof char[]) {
                values = createCollection(List.class, 1);

                final T value = convertValue(converted, converted.getClass(), type);
                if (value != null) {
                    values.add(value);
                }
            } else if (converted.getClass().isArray()) {
                final int length = Array.getLength(converted);
                values = createCollection(List.class, length);
                for (int i = 0; i < length; i++) {
                    final Object o = Array.get(converted, i);
                    if (o != null) {
                        final T value = convertValue(o, o.getClass(), type);
                        if (value != null) {
                            values.add(value);
                        }
                    }
                }
            } else if (Collection.class.isAssignableFrom(converted.getClass())) {
                final Collection<?> col = (Collection<?>) converted;
                values = createCollection(converted.getClass(), col.size());
                for (Object o : col) {
                    if (o != null) {
                        final T value = convertValue(o, o.getClass(), type);
                        if (value != null) {
                            values.add(value);
                        }
                    }
                }
            } else {
                values = createCollection(List.class, 1);

                final T value = convertValue(converted, converted.getClass(), type);
                if (value != null) {
                    values.add(value);
                }
            }
        }
        return values;
    }

    /**
     * Converts the supplied value to the target type. If a custom transcoder has
     * been configured it is used. Otherwise the type converter from the
     * evaluation context is used.
     *
     * @param  <T>  either String or byte[]
     * @param  value  to convert
     * @param  sourceType  to convert from
     * @param  targetType  to convert to
     *
     * @return  converted value
     */
    @SuppressWarnings("unchecked")
    protected <T> T convertValue(final Object value, final Class<?> sourceType, final Class<T> targetType) {
        T converted;
        if (transcoder != null) {
            if (byte[].class == targetType) {
                converted = (T) transcoder.encodeBinaryValue(value);
            } else if (String.class == targetType) {
                converted = (T) transcoder.encodeStringValue(value);
            } else {
                throw new IllegalArgumentException("targetType must be either String.class or byte[].class");
            }
        } else {
            converted = (T) evaluationContext.getTypeConverter().convertValue(value,
                    TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType));
        }
        return converted;
    }

    @Override
    public void setStringValues(final Object object, final Collection<String> values) {
        setValues(object, values, String.class);
    }

    @Override
    public void setBinaryValues(final Object object, final Collection<byte[]> values) {
        setValues(object, values, byte[].class);
    }

    /**
     * Uses the configured expression and evaluation context to set values on the
     * supplied object. If a custom transcoder has been configured it is executed
     * on the values before they are passed to the expression.
     *
     * @param  <T>  either String or byte[]
     * @param  object  to set values on
     * @param  values  to set
     * @param  type  of objects in the collection
     */
    protected <T> void setValues(final Object object, final Collection<T> values, final Class<T> type) {
        if (transcoder != null) {
            final Collection<Object> newValues = createCollection(values.getClass(), values.size());
            for (T t : values) {
                if (byte[].class == type) {
                    newValues.add(transcoder.decodeBinaryValue((byte[]) t));
                } else if (String.class == type) {
                    newValues.add(transcoder.decodeStringValue((String) t));
                } else {
                    throw new IllegalArgumentException("type must be either String.class or byte[].class");
                }
            }
            expression.setValue(evaluationContext, object, newValues);
        } else {
            if (values != null && values.size() == 1) {
                expression.setValue(evaluationContext, object, values.iterator().next());
            } else {
                expression.setValue(evaluationContext, object, values);
            }
        }
    }

    @Override
    public String toString() {
        return String.format("[%s@%d::attribute=%s, expression=%s, evaluationContext=%s, " + "transcoder=%s]",
                getClass().getName(), hashCode(), attribute, expression, evaluationContext, transcoder);
    }

    /**
     * Creates a best fit collection for the supplied type.
     *
     * @param  <T>  collection type
     * @param  type  of collection to create
     * @param  size  of the collection
     *
     * @return  collection
     */
    protected static <T> Collection<T> createCollection(final Class<?> type, final int size) {
        Collection<T> c;
        if (List.class.isAssignableFrom(type)) {
            if (LinkedList.class.isAssignableFrom(type)) {
                c = new LinkedList<>();
            } else {
                c = new ArrayList<>(size);
            }
        } else if (Set.class.isAssignableFrom(type)) {
            if (LinkedHashSet.class.isAssignableFrom(type)) {
                c = new LinkedHashSet<>(size);
            } else if (TreeSet.class.isAssignableFrom(type)) {
                c = new TreeSet<>();
            } else {
                c = new HashSet<>(size);
            }
        } else {
            c = new ArrayList<>(size);
        }
        return c;
    }
}