/*
* Copyright 2012 Jakob Külzer
*
* 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.om.core.impl.mapping.extractor;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.om.core.api.annotation.Collection;
import org.om.core.api.annotation.CollectionMode;
import org.om.core.api.annotation.Id;
import org.om.core.api.annotation.MapKeyStrategy;
import org.om.core.api.annotation.Mapped;
import org.om.core.api.annotation.MissingStrategy;
import org.om.core.api.annotation.NULL;
import org.om.core.api.annotation.Property;
import org.om.core.api.exception.MappingException;
import org.om.core.api.exception.MissingException;
import org.om.core.api.mapping.MappedField;
import org.om.core.api.mapping.extractor.FieldMappingExtractor;
import org.om.core.api.mapping.field.Mapping;
import org.om.core.api.mapping.field.PropertyMapping;
import org.om.core.impl.mapping.ImmutableCollectionMapping;
import org.om.core.impl.mapping.ImmutableMappedField;
import org.om.core.impl.mapping.field.ImmutableIdMapping;
import org.om.core.impl.mapping.field.ImmutablePropertyMapping;
import org.om.core.impl.mapping.field.ImmutableReferenceMapping;
import org.om.core.impl.util.ClassUtils;
import org.om.core.impl.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FieldMappingExtractorImpl implements FieldMappingExtractor {
private static final Logger LOGGER = LoggerFactory.getLogger(FieldMappingExtractorImpl.class);
private void checkMapping(String fieldname, boolean isProperty, boolean isCollection, boolean isId) {
if (isCollection && isProperty && isId) {
throw new MappingException("Field " + fieldname + " is mapped as @Id, @Property and @Collection. Make up your mind!");
}
if (isProperty && isId) {
throw new MappingException("Cannot map field " + fieldname + " @Id and @Property. Properties cannot be @Id fields!");
}
if (isCollection && isId) {
throw new MappingException("Cannot map field " + fieldname + " @Id and @Collection. Collections cannot be @Id fields!");
}
}
public MappedField extract(Field field) {
final Mapped mapped = field.getAnnotation(Mapped.class);
final Id id = field.getAnnotation(Id.class);
final Collection collection = field.getAnnotation(Collection.class);
final Property property = field.getAnnotation(Property.class);
final boolean isMappedField = mapped != null || id != null || collection != null || property != null;
if (!isMappedField) {
LOGGER.trace("Field {} is not annotated, ignoring in mapping.", field.getName());
return null;
}
LOGGER.trace("Found annotated field {}", field.getName());
final Mapping mapping = extractMapping(field);
final Class<?> fieldType = field.getType();
final Class<?> implementationType = mapping.getImplementationType();
final boolean validImplementationType = String.class.equals(implementationType) || EntityUtils.isEntity(implementationType)
|| ClassUtils.isPrimitiveOrAutoboxed(implementationType);
if (!validImplementationType)
throw new MappingException("Target type " + fieldType + " is not an entity.");
final MappedField mappedField = new ImmutableMappedField(field.getName(), fieldType, mapping, getMissingStrategy(mapped), getMissingException(mapped));
return mappedField;
}
private Mapping extractCollectionMapping(Mapped mapped, Collection collection, Field field) {
final Class<?> declaredType = collection.targetType();
Class<?> fieldType = field.getType();
final boolean isList = List.class.equals(fieldType);
final boolean isSet = Set.class.equals(fieldType);
final boolean isMap = Map.class.equals(fieldType);
final boolean isSupportedType = isList || isSet || isMap;
if (!isSupportedType)
throw new MappingException("Collection field " + field.getName() + " is of type " + fieldType
+ " but only java.util.List, java.util.Map or java.util.Set are supported.");
final String location;
if (collection.location() == null || collection.location().isEmpty()) {
location = field.getName();
} else {
location = collection.location();
}
final CollectionMode collectionMapping = collection.mode();
final MapKeyStrategy keyMapStrategy = collection.keyStrategy();
final Class<?> implementationType = getImplementationType(mapped, declaredType);
return new ImmutableCollectionMapping(fieldType, declaredType, implementationType, location, collectionMapping, keyMapStrategy);
}
private Mapping extractIdMapping(Id id, Field field) {
return ImmutableIdMapping.INSTANCE;
}
/**
* Extracts a {@link PropertyMapping} from the given field.
*
* TODO: this should go into a factory supporting different types of
* property mappings.
*
* @param field
* @return the extracted property mapping.
* @throws MappingException
* if the mapping cannot be constructed or if there's no
* mapping.
*/
public Mapping extractMapping(Field field) {
if (field == null)
throw new IllegalArgumentException("Cannot extract property mapping from field, it's null.");
final String fieldname = field.getName();
LOGGER.trace("Extracting mapping for field {}", fieldname);
final Mapped mapped = field.getAnnotation(Mapped.class);
final Property annotation = field.getAnnotation(Property.class);
final Collection collection = field.getAnnotation(Collection.class);
final Id id = field.getAnnotation(Id.class);
final boolean isProperty = annotation != null;
final boolean isCollection = collection != null;
final boolean isId = id != null;
checkMapping(fieldname, isProperty, isCollection, isId);
final Mapping mapping;
if (isId) {
mapping = extractIdMapping(id, field);
} else if (isProperty) {
mapping = extractPropertyMapping(mapped, annotation, field);
} else {
mapping = extractCollectionMapping(mapped, collection, field);
}
return mapping;
}
Mapping extractPropertyMapping(Mapped mapped, Property annotation, Field field) {
final String fieldname = field.getName();
final Class<?> declaredType = field.getType();
final boolean primitiveOrAutoboxed = ClassUtils.isPrimitiveOrAutoboxed(declaredType) || String.class.equals(declaredType);
final String propertyName;
final boolean hasNameSetOnAnnotation = annotation.name() != null && annotation.name().length() > 1;
if (hasNameSetOnAnnotation) {
propertyName = annotation.name();
} else {
propertyName = fieldname;
}
if (primitiveOrAutoboxed) {
return new ImmutablePropertyMapping(propertyName, declaredType, annotation.defaultValue());
} else {
Class<?> implementationType = getImplementationType(mapped, declaredType);
return new ImmutableReferenceMapping(declaredType, implementationType, propertyName, annotation.lookupMode());
}
}
/**
* Returns the implementation type to be used for the given {@link Mapped}
* annotation and declared type. If the given annotation is null or the
* implementation type returned by it is null, this will return the declared
* type, otherwise the specified implementation type.
*
* @param mapped
* @param declaredType
* @return
*/
private Class<?> getImplementationType(Mapped mapped, Class<?> declaredType) {
return mapped == null || mapped.implementationType() == NULL.class ? declaredType : mapped.implementationType();
}
private Class<? extends RuntimeException> getMissingException(Mapped mapped) {
return mapped == null ? MissingException.class : mapped.missingException();
}
private MissingStrategy getMissingStrategy(Mapped mapped) {
return mapped == null ? MissingStrategy.ReturnNull : mapped.missingStrategy();
}
}