com.frank.search.solr.core.convert.MappingSolrConverter.java Source code

Java tutorial

Introduction

Here is the source code for com.frank.search.solr.core.convert.MappingSolrConverter.java

Source

/*
 * Copyright 2012 - 2015 the original author or authors.
 *
 * 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.
 */
package com.frank.search.solr.core.convert;

import com.frank.search.solr.core.mapping.SolrPersistentEntity;
import com.frank.search.solr.core.mapping.SolrPersistentProperty;
import com.frank.search.solr.core.query.Criteria;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * Implementation of {@link SolrConverter} to read/write
 * {@link org.apache.solr.common.SolrDocument}/
 * {@link org.apache.solr.common.SolrInputDocument}. <br/>
 * 
 * @author Christoph Strobl
 * @author Francisco Spaeth
 */
public class MappingSolrConverter extends SolrConverterBase
        implements SolrConverter, ApplicationContextAware, InitializingBean {

    private enum WildcardPosition {

        LEADING {

            @Override
            public boolean match(String fieldName, String candidate) {
                return StringUtils.endsWith(candidate, removeWildcard(fieldName));
            }

            @Override
            public String extractName(String fieldName, String dynamicFieldName) {
                Assert.isTrue(match(fieldName, dynamicFieldName),
                        "dynamicFieldName must be derivated from fieldName");
                return StringUtils.removeEnd(dynamicFieldName, removeWildcard(fieldName));
            }

            @Override
            public String createName(String fieldName, String name) {
                return removeWildcard(fieldName) + name;
            }
        },

        TRAILING {

            @Override
            public boolean match(String fieldName, String candidate) {
                return StringUtils.startsWith(candidate, removeWildcard(fieldName));
            }

            @Override
            public String extractName(String fieldName, String dynamicFieldName) {
                Assert.isTrue(match(fieldName, dynamicFieldName),
                        "dynamicFieldName must be derivated from fieldName");
                return StringUtils.removeStart(dynamicFieldName, removeWildcard(fieldName));
            }

            @Override
            public String createName(String fieldName, String name) {
                return name + removeWildcard(fieldName);
            }
        };

        public static WildcardPosition getAppropriate(String fieldName) {
            if (StringUtils.startsWith(fieldName, Criteria.WILDCARD)) {
                return WildcardPosition.LEADING;
            } else {
                return WildcardPosition.TRAILING;
            }
        }

        String removeWildcard(String fieldName) {
            return StringUtils.remove(fieldName, Criteria.WILDCARD);
        }

        public abstract boolean match(String fieldName, String candidate);

        public abstract String extractName(String fieldName, String dynamicFieldName);

        public abstract String createName(String fieldName, String name);
    }

    private final MappingContext<? extends SolrPersistentEntity<?>, SolrPersistentProperty> mappingContext;
    private final EntityInstantiators instantiators = new EntityInstantiators();

    @SuppressWarnings("unused") //
    private ApplicationContext applicationContext;

    public MappingSolrConverter(
            MappingContext<? extends SolrPersistentEntity<?>, SolrPersistentProperty> mappingContext) {
        Assert.notNull(mappingContext);

        this.mappingContext = mappingContext;
    }

    @Override
    public MappingContext<? extends SolrPersistentEntity<?>, SolrPersistentProperty> getMappingContext() {
        return mappingContext;
    }

    @Override
    public <S, R> List<R> read(SolrDocumentList source, Class<R> type) {
        if (source == null) {
            return Collections.emptyList();
        }

        List<R> resultList = new ArrayList<R>(source.size());
        TypeInformation<R> typeInformation = ClassTypeInformation.from(type);
        for (Map<String, ?> item : source) {
            resultList.add(read(typeInformation, item));
        }

        return resultList;
    }

    @Override
    public <R> R read(Class<R> type, Map<String, ?> source) {
        return read(ClassTypeInformation.from(type), source);
    }

    @SuppressWarnings("unchecked")
    protected <S extends Object> S read(TypeInformation<S> targetTypeInformation, Map<String, ?> source) {
        if (source == null) {
            return null;
        }
        Assert.notNull(targetTypeInformation);
        Class<S> rawType = targetTypeInformation.getType();

        // in case there's a custom conversion for the document
        if (hasCustomReadTarget(source.getClass(), rawType)) {
            return convert(source, rawType);
        }

        SolrPersistentEntity<S> entity = (SolrPersistentEntity<S>) mappingContext.getPersistentEntity(rawType);
        return read(entity, source, null);
    }

    private <S extends Object> S read(final SolrPersistentEntity<S> entity, final Map<String, ?> source,
            Object parent) {
        ParameterValueProvider<SolrPersistentProperty> parameterValueProvider = getParameterValueProvider(entity,
                source, parent);

        EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
        final S instance = instantiator.createInstance(entity, parameterValueProvider);
        final PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(
                entity.getPropertyAccessor(instance), getConversionService());

        entity.doWithProperties(new PropertyHandler<SolrPersistentProperty>() {

            @Override
            public void doWithPersistentProperty(SolrPersistentProperty persistentProperty) {
                if (entity.isConstructorArgument(persistentProperty)) {
                    return;
                }

                Object o = getValue(persistentProperty, source, instance);
                if (o != null) {
                    accessor.setProperty(persistentProperty, o);
                }
            }
        });

        return instance;
    }

    protected Object getValue(SolrPersistentProperty property, Object source, Object parent) {
        SolrPropertyValueProvider provider = new SolrPropertyValueProvider(source, parent);
        return provider.getPropertyValue(property);
    }

    private ParameterValueProvider<SolrPersistentProperty> getParameterValueProvider(SolrPersistentEntity<?> entity,
            Map<String, ?> source, Object parent) {

        SolrPropertyValueProvider provider = new SolrPropertyValueProvider(source, parent);
        PersistentEntityParameterValueProvider<SolrPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<SolrPersistentProperty>(
                entity, provider, parent);

        return parameterProvider;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void write(Object source, @SuppressWarnings("rawtypes") Map target) {

        if (source == null) {
            return;
        }

        Class<? extends Object> sourceClass = source.getClass();

        if (hasCustomWriteTarget(sourceClass, SolrInputDocument.class)
                && canConvert(sourceClass, SolrInputDocument.class)) {

            SolrInputDocument convertedDocument = convert(source, SolrInputDocument.class);
            target.putAll(convertedDocument);
        } else {

            SolrPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
            write(source, target, entity);
        }

    }

    @SuppressWarnings("rawtypes")
    protected void write(Object source, final Map target, SolrPersistentEntity<?> entity) {

        final PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(
                entity.getPropertyAccessor(source), getConversionService());

        entity.doWithProperties(new PropertyHandler<SolrPersistentProperty>() {

            @SuppressWarnings("unchecked")
            @Override
            public void doWithPersistentProperty(SolrPersistentProperty persistentProperty) {

                Object value = accessor.getProperty(persistentProperty);
                if (value == null || persistentProperty.isReadonly()) {
                    return;
                }

                if (persistentProperty.containsWildcard() && !persistentProperty.isMap()) {
                    throw new IllegalArgumentException("Field '" + persistentProperty.getFieldName()
                            + "' must not contain wildcards. Consider excluding Field from beeing indexed.");
                }

                Collection<SolrInputField> fields;
                if (persistentProperty.isMap() && persistentProperty.containsWildcard()) {
                    fields = writeWildcardMapPropertyToTarget(target, persistentProperty, (Map<?, ?>) value);
                } else {
                    fields = writeRegularPropertyToTarget(target, persistentProperty, value);
                }

                if (persistentProperty.isBoosted()) {
                    for (SolrInputField field : fields) {
                        field.setBoost(persistentProperty.getBoost());
                    }
                }
            }
        });

        if (entity.isBoosted() && target instanceof SolrInputDocument) {
            ((SolrInputDocument) target).setDocumentBoost(entity.getBoost());
        }

    }

    private Collection<SolrInputField> writeWildcardMapPropertyToTarget(Map<? super Object, ? super Object> target,
            SolrPersistentProperty persistentProperty, Map<?, ?> fieldValue) {

        TypeInformation<?> mapTypeInformation = persistentProperty.getTypeInformation().getMapValueType();
        Class<?> rawMapType = mapTypeInformation.getType();
        String fieldName = persistentProperty.getFieldName();

        Collection<SolrInputField> fields = new ArrayList<SolrInputField>();

        for (Map.Entry<?, ?> entry : fieldValue.entrySet()) {

            Object value = entry.getValue();
            String key = entry.getKey().toString();

            if (persistentProperty.isDynamicProperty()) {
                key = WildcardPosition.getAppropriate(key).createName(fieldName, key);
            }

            SolrInputField field = new SolrInputField(key);

            if (value instanceof Iterable) {

                for (Object o : (Iterable<?>) value) {
                    field.addValue(convertToSolrType(rawMapType, o), 1f);
                }
            } else {

                if (rawMapType.isArray()) {
                    for (Object o : (Object[]) value) {
                        field.addValue(convertToSolrType(rawMapType, o), 1f);
                    }
                } else {
                    field.addValue(convertToSolrType(rawMapType, value), 1f);
                }

            }

            target.put(key, field);
            fields.add(field);
        }

        return fields;
    }

    private Collection<SolrInputField> writeRegularPropertyToTarget(
            final Map<? super Object, ? super Object> target, SolrPersistentProperty persistentProperty,
            Object fieldValue) {

        SolrInputField field = new SolrInputField(persistentProperty.getFieldName());

        if (persistentProperty.isCollectionLike()) {
            Collection<?> collection = asCollection(fieldValue);
            for (Object o : collection) {
                if (o != null) {
                    field.addValue(convertToSolrType(persistentProperty.getType(), o), 1f);
                }
            }
        } else {
            field.setValue(convertToSolrType(persistentProperty.getType(), fieldValue), 1f);
        }

        target.put(persistentProperty.getFieldName(), field);

        return Collections.singleton(field);

    }

    private Object convertToSolrType(Class<?> type, Object value) {
        if (type == null || value == null) {
            return value;
        }

        if (isSimpleType(type)) {
            return value;
        } else if (hasCustomWriteTarget(value.getClass())) {
            Class<?> targetType = getCustomWriteTargetType(value.getClass());
            if (canConvert(value.getClass(), targetType)) {
                return convert(value, targetType);
            }
        }

        return value;
    }

    private static Collection<?> asCollection(Object source) {

        if (source instanceof Collection) {
            return (Collection<?>) source;
        }

        return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private class SolrPropertyValueProvider implements PropertyValueProvider<SolrPersistentProperty> {

        private final Object source;
        private final Object parent;

        public SolrPropertyValueProvider(Object source, Object parent) {
            this.source = source;
            this.parent = parent;
        }

        @SuppressWarnings("unchecked")
        @Override
        public <T> T getPropertyValue(SolrPersistentProperty property) {
            if (source instanceof Map<?, ?>) {
                return (T) readValue((Map<String, ?>) source, property, parent);
            }

            return readValue(source, property.getTypeInformation(), this.parent);
        }

        @SuppressWarnings("unchecked")
        private <T> T readValue(Map<String, ?> value, SolrPersistentProperty property, Object parent) {
            if (value == null) {
                return null;
            }
            if (property.containsWildcard()) {
                return (T) readWildcard(value, property, parent);
            }
            if (property.isScoreProperty()) {
                return (T) readScore(value, property, parent);
            }
            return readValue(value.get(property.getFieldName()), property.getTypeInformation(), parent);
        }

        @SuppressWarnings("unchecked")
        private <T> T readScore(Map<String, ?> value, SolrPersistentProperty property, Object parent) {
            return (T) value.get("score");
        }

        @SuppressWarnings("unchecked")
        private <T> T readValue(Object value, TypeInformation<?> type, Object parent) {
            if (value == null) {
                return null;
            }

            Assert.notNull(type);
            Class<?> rawType = type.getType();
            if (hasCustomReadTarget(value.getClass(), rawType)) {
                return (T) convert(value, rawType);
            }

            Object documentValue = null;
            if (value instanceof SolrInputField) {
                documentValue = ((SolrInputField) value).getValue();
            } else {
                documentValue = value;
            }

            if (documentValue instanceof Collection) {
                return (T) readCollection((Collection<?>) documentValue, type, parent);
            } else if (canConvert(documentValue.getClass(), rawType)) {
                return (T) convert(documentValue, rawType);
            }

            return (T) documentValue;

        }

        private Object readWildcard(Map<String, ?> source, SolrPersistentProperty property, Object parent) {

            WildcardPosition wildcardPosition = WildcardPosition.getAppropriate(property.getFieldName());

            if (property.isMap()) {
                return readWildcardMap(source, property, parent, wildcardPosition);
            } else if (property.isCollectionLike()) {
                return readWildcardCollectionLike(source, property, parent, wildcardPosition);
            } else {

                for (Map.Entry<String, ?> potentialMatch : source.entrySet()) {

                    if (wildcardPosition.match(property.getFieldName(), potentialMatch.getKey())) {
                        return getValue(property, potentialMatch.getValue(), parent);
                    }
                }
            }

            return null;
        }

        private Object readWildcardCollectionLike(Map<String, ?> source, SolrPersistentProperty property,
                Object parent, WildcardPosition wildcardPosition) {

            Class<?> genericTargetType = property.getComponentType() != null ? property.getComponentType()
                    : Object.class;

            List<Object> values = new ArrayList<Object>();

            for (Map.Entry<String, ?> potentialMatch : source.entrySet()) {

                if (!wildcardPosition.match(property.getFieldName(), potentialMatch.getKey())) {
                    continue;
                }

                Object value = potentialMatch.getValue();

                if (value instanceof Iterable) {

                    for (Object o : (Iterable<?>) value) {
                        values.add(readValue(property, o, parent, genericTargetType));
                    }
                } else {

                    Object o = readValue(property, potentialMatch.getValue(), parent, genericTargetType);
                    if (o instanceof Collection) {
                        values.addAll((Collection<?>) o);
                    } else {
                        values.add(o);
                    }
                }
            }

            return values.isEmpty() ? null : (property.isArray() ? values.toArray() : values);
        }

        private Object readWildcardMap(Map<String, ?> source, SolrPersistentProperty property, Object parent,
                WildcardPosition wildcardPosition) {

            TypeInformation<?> mapTypeInformation = property.getTypeInformation().getMapValueType();
            Class<?> rawMapType = mapTypeInformation.getType();

            Class<?> genericTargetType;
            if (mapTypeInformation.getTypeArguments() != null && !mapTypeInformation.getTypeArguments().isEmpty()) {
                genericTargetType = mapTypeInformation.getTypeArguments().get(0).getType();
            } else {
                genericTargetType = Object.class;
            }

            Map<String, Object> values;
            if (LinkedHashMap.class.isAssignableFrom(property.getActualType())) {
                values = new LinkedHashMap<String, Object>();
            } else {
                values = new HashMap<String, Object>();
            }

            for (Map.Entry<String, ?> potentialMatch : source.entrySet()) {

                String key = potentialMatch.getKey();

                if (!wildcardPosition.match(property.getFieldName(), key)) {
                    continue;
                }

                if (property.isDynamicProperty()) {
                    key = wildcardPosition.extractName(property.getFieldName(), key);
                }
                Object value = potentialMatch.getValue();

                if (value instanceof Iterable) {

                    if (rawMapType.isArray() || ClassUtils.isAssignable(rawMapType, value.getClass())) {
                        List<Object> nestedValues = new ArrayList<Object>();
                        for (Object o : (Iterable<?>) value) {
                            nestedValues.add(readValue(property, o, parent, genericTargetType));
                        }
                        values.put(key, (rawMapType.isArray() ? nestedValues.toArray() : nestedValues));
                    } else {
                        throw new IllegalArgumentException("Incompartible types found. Expected " + rawMapType
                                + " for " + property.getName() + " with name " + property.getFieldName()
                                + ", but found " + value.getClass());
                    }
                } else {

                    if (rawMapType.isArray() || ClassUtils.isAssignable(rawMapType, List.class)) {
                        ArrayList<Object> singletonArrayList = new ArrayList<Object>(1);
                        Object read = readValue(property, value, parent, genericTargetType);
                        singletonArrayList.add(read);
                        values.put(key, (rawMapType.isArray() ? singletonArrayList.toArray() : singletonArrayList));

                    } else {
                        values.put(key, getValue(property, value, parent));
                    }
                }
            }

            return values.isEmpty() ? null : values;
        }

        private Object readValue(SolrPersistentProperty property, Object o, Object parent, Class<?> target) {

            Object value = getValue(property, o, parent);
            if (value == null || target == null || target.equals(Object.class)) {
                return value;
            }

            if (canConvert(value.getClass(), target)) {
                return convert(value, target);
            }

            return value;
        }

        private Object readCollection(Collection<?> source, TypeInformation<?> type, Object parent) {
            Assert.notNull(type);

            Class<?> collectionType = type.getType();
            if (CollectionUtils.isEmpty(source)) {
                return source;
            }

            collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;

            Collection<Object> items;
            if (type.getType().isArray()) {
                items = new ArrayList<Object>();
            } else {
                items = CollectionFactory.createCollection(collectionType, source.size());
            }

            TypeInformation<?> componentType = type.getComponentType();

            Iterator<?> it = source.iterator();
            while (it.hasNext()) {
                items.add(readValue(it.next(), componentType, parent));
            }

            return type.getType().isArray() ? convertItemsToArrayOfType(type, items) : items;
        }

        private Object convertItemsToArrayOfType(TypeInformation<?> type, Collection<Object> items) {

            Object[] newArray = (Object[]) java.lang.reflect.Array.newInstance(type.getActualType().getType(),
                    items.size());
            Object[] itemsArray = items.toArray();
            for (int i = 0; i < itemsArray.length; i++) {
                newArray[i] = itemsArray[i];
            }
            return newArray;
        }
    }
}