Java tutorial
/* * Copyright 2012 - 2014 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 org.springframework.data.solr.core.convert; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; 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.solr.core.mapping.SolrPersistentEntity; import org.springframework.data.solr.core.mapping.SolrPersistentProperty; import org.springframework.data.solr.core.query.Criteria; 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; /** * Implementation of {@link SolrConverter} to read/write {@link org.apache.solr.common.SolrDocumen}/ * {@link SolrInputDocument}. <br/> * * @author Christoph Strobl * @author Francisco Spaeth */ public class MappingSolrConverter extends SolrConverterBase implements SolrConverter, ApplicationContextAware, InitializingBean { private enum WildcardPosition { LEADING, TRAILING } 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; } if (hasCustomWriteTarget(source.getClass(), SolrInputDocument.class) && canConvert(source.getClass(), SolrInputDocument.class)) { SolrInputDocument convertedDocument = convert(source, SolrInputDocument.class); target.putAll(convertedDocument); return; } TypeInformation<? extends Object> type = ClassTypeInformation.from(source.getClass()); write(type, source, target); } protected void write(TypeInformation<?> type, Object source, @SuppressWarnings("rawtypes") Map target) { Assert.notNull(type); SolrPersistentEntity<?> entity = mappingContext.getPersistentEntity(source.getClass()); 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."); } Object fieldValue = value; if (persistentProperty.isMap() && persistentProperty.containsWildcard()) { TypeInformation<?> mapTypeInformation = persistentProperty.getTypeInformation() .getMapValueType(); Class<?> rawMapType = mapTypeInformation.getType(); Map<?, ?> map = (Map<?, ?>) fieldValue; for (Map.Entry<?, ?> entry : map.entrySet()) { String mappedFieldName = entry.getKey().toString(); SolrInputField field = new SolrInputField(mappedFieldName); if (entry.getValue() instanceof Iterable) { for (Object o : (Iterable<?>) entry.getValue()) { field.addValue(convertToSolrType(rawMapType, o), 1f); } } else { if (rawMapType.isArray()) { for (Object o : (Object[]) entry.getValue()) { field.addValue(convertToSolrType(rawMapType, o), 1f); } } else { field.addValue(convertToSolrType(rawMapType, entry.getValue()), 1f); } } target.put(mappedFieldName, field); } return; } 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); if (persistentProperty.isBoosted()) { field.setBoost(persistentProperty.getBoost()); } } }); if (entity.isBoosted() && target instanceof SolrInputDocument) { ((SolrInputDocument) target).setDocumentBoost(entity.getBoost()); } } 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); } return readValue(value.get(property.getFieldName()), property.getTypeInformation(), parent); } @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; } @SuppressWarnings({ "rawtypes", "unchecked" }) private Object readWildcard(Map<String, ?> source, SolrPersistentProperty property, Object parent) { String fieldName = StringUtils.remove(property.getFieldName(), Criteria.WILDCARD); WildcardPosition wildcardPosition = StringUtils.startsWith(property.getFieldName(), Criteria.WILDCARD) ? WildcardPosition.LEADING : WildcardPosition.TRAILING; if (property.isMap()) { TypeInformation<?> mapTypeInformation = property.getTypeInformation().getMapValueType(); Class<?> rawMapType = mapTypeInformation.getType(); Class<?> genericTargetType = (mapTypeInformation.getTypeArguments() != null && !mapTypeInformation.getTypeArguments().isEmpty()) ? mapTypeInformation.getTypeArguments().get(0).getType() : Object.class; Map<String, Object> values = LinkedHashMap.class.isAssignableFrom(property.getActualType()) ? new LinkedHashMap<String, Object>() : new HashMap<String, Object>(); for (Map.Entry<String, ?> potentialMatch : source.entrySet()) { if (isWildcardFieldNameMatch(fieldName, wildcardPosition, potentialMatch.getKey())) { 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(potentialMatch.getKey(), (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); singletonArrayList.add( readValue(property, potentialMatch.getValue(), parent, genericTargetType)); values.put(potentialMatch.getKey(), (rawMapType.isArray() ? singletonArrayList.toArray() : singletonArrayList)); } else { values.put(potentialMatch.getKey(), getValue(property, potentialMatch.getValue(), parent)); } } } } return values.isEmpty() ? null : values; } else if (property.isCollectionLike()) { Class<?> genericTargetType = property.getComponentType() != null ? property.getComponentType() : Object.class; List<Object> values = new ArrayList<Object>(); for (Map.Entry<String, ?> potentialMatch : source.entrySet()) { if (isWildcardFieldNameMatch(fieldName, wildcardPosition, potentialMatch.getKey())) { 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); } else { for (Map.Entry<String, ?> potentialMatch : source.entrySet()) { if (StringUtils.contains(potentialMatch.getKey(), fieldName)) { return getValue(property, potentialMatch.getValue(), parent); } } } return null; } 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 boolean isWildcardFieldNameMatch(String fieldname, WildcardPosition type, String candidate) { switch (type) { case LEADING: return StringUtils.endsWith(candidate, fieldname); case TRAILING: return StringUtils.startsWith(candidate, fieldname); } return false; } 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 = type.getType().isArray() ? new ArrayList<Object>() : 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; } } }