package fi.otavanopisto.muikku.search;
import java.beans.IntrospectionException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import fi.otavanopisto.muikku.schooldata.SchoolDataIdentifier;
import fi.otavanopisto.muikku.search.annotations.IndexField;
import fi.otavanopisto.muikku.search.annotations.IndexId;
import fi.otavanopisto.muikku.search.annotations.IndexIgnore;
import fi.otavanopisto.muikku.search.annotations.Indexable;
@Stateless
public class IndexEntityProcessor {
@Inject
private Logger logger;
public Map<String, Object> process(Object entity) throws IllegalArgumentException, IllegalAccessException, IntrospectionException, InvocationTargetException,
IndexIdMissingException {
if (isIndexable(entity)) {
String id = getEnitityId(entity);
if (StringUtils.isBlank(id)) {
throw new IndexIdMissingException("Indexable object is missing an id.");
}
Map<String, Object> indexObject = new HashMap<String, Object>();
indexObject.put("id", id);
for (Method indexableGetter : getIndexableGetters(entity)) {
String fieldName = StringUtils.uncapitalize(indexableGetter.getName().substring(3));
IndexField indexField = findIndexField(indexableGetter);
if (indexField != null) {
if (indexField.skip()) {
continue;
}
String name = indexField.name();
if (StringUtils.isNotBlank(name)) {
fieldName = name;
}
}
Object fieldValue = indexableGetter.invoke(entity);
if (indexField != null && indexField.toId()) {
if (fieldValue != null) {
if (fieldValue instanceof Collection) {
Collection<?> collection = (Collection<?>) fieldValue;
Set<String> ids = new HashSet<String>();
for (Object o : collection) {
if (o != null) {
if (o instanceof SchoolDataIdentifier)
ids.add(((SchoolDataIdentifier) o).toId());
else {
logger.severe(String.format("@Indexable toId for Collection must be Collection<SchoolDataIdentifier> but was Collection<%s>", o.getClass().getName()));
}
}
}
fieldValue = ids;
} else if (fieldValue instanceof SchoolDataIdentifier) {
fieldValue = ((SchoolDataIdentifier) fieldValue).toId();
} else {
logger.severe(String.format("@Indexable toId must be SchoolDataIdentifier but was %s", fieldValue.getClass().getName()));
}
}
}
indexObject.put(fieldName, fieldValue);
}
return indexObject;
}
return null;
}
private IndexField findIndexField(Method method) {
IndexField indexField = method.getAnnotation(IndexField.class);
if (indexField != null) {
return indexField;
}
Class<?> declaringClass = method.getDeclaringClass();
if (declaringClass != null) {
if ((declaringClass.getSuperclass() != null) && (!declaringClass.getSuperclass().equals(Object.class))) {
Class<?> superclass = declaringClass.getSuperclass();
try {
Method superclassMethod = superclass.getMethod(method.getName());
indexField = superclassMethod.getAnnotation(IndexField.class);
if (indexField != null) {
return indexField;
}
} catch (NoSuchMethodException | SecurityException e) {
}
}
for (Class<?> declaringClassInterface : declaringClass.getInterfaces()) {
try {
Method declaringClassInterfaceMethod = declaringClassInterface.getMethod(method.getName());
indexField = declaringClassInterfaceMethod.getAnnotation(IndexField.class);
if (indexField != null) {
return indexField;
}
} catch (NoSuchMethodException | SecurityException e) {
}
}
}
return null;
}
private boolean isIndexable(Object entity) {
if (entity != null) {
return isIndexable(entity.getClass());
}
return false;
}
private boolean isIndexable(Class<?> clazz) {
if (clazz.isAnnotationPresent(Indexable.class)) {
return true;
}
for (Class<?> entityInterface : clazz.getInterfaces()) {
if (entityInterface.isAnnotationPresent(Indexable.class)) {
return true;
}
}
if ((clazz.getSuperclass() != null) && (!Object.class.equals(clazz))) {
return isIndexable(clazz.getSuperclass());
}
return false;
}
private String getEnitityId(Object entity) {
Method method = getEntityIdMethod(entity);
if (method != null) {
try {
return method.invoke(entity).toString();
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
logger.log(Level.SEVERE, "Could not invoke Indexable IndexId getter", e);
}
}
Field field = getEntityIdField(entity);
if (field != null) {
try {
field.setAccessible(true);
return field.get(entity).toString();
} catch (IllegalArgumentException | IllegalAccessException e) {
logger.log(Level.SEVERE, "Could not get Indexable IndexId field value", e);
}
}
return null;
}
private Method getEntityIdMethod(Object entity) {
Class<?> currentEntityClass = entity.getClass();
while (currentEntityClass != null && currentEntityClass != Object.class) {
Method method = getMethodByAnnotation(currentEntityClass, IndexId.class);
if (method != null) {
return method;
}
for (Class<?> currentEntityClassInterface : currentEntityClass.getInterfaces()) {
Method interfaceMethod = getMethodByAnnotation(currentEntityClassInterface, IndexId.class);
if (interfaceMethod != null) {
return interfaceMethod;
}
}
currentEntityClass = currentEntityClass.getSuperclass();
}
return null;
}
private List<Method> getIndexableGetters(Object entity) {
List<Method> result = new ArrayList<>();
Class<?> currentEntityClass = entity.getClass();
while (currentEntityClass != null && currentEntityClass != Object.class) {
for (Method method : currentEntityClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(IndexIgnore.class)) {
continue;
}
if (method.isAnnotationPresent(IndexId.class)) {
continue;
}
if (StringUtils.startsWith(method.getName(), "get")) {
result.add(method);
}
}
currentEntityClass = currentEntityClass.getSuperclass();
}
return result;
}
private Field getEntityIdField(Object entity) {
Class<?> currentEntityClass = entity.getClass();
while (currentEntityClass != null && currentEntityClass != Object.class) {
Field field = getFieldByAnnotation(currentEntityClass, IndexId.class);
if (field != null) {
return field;
}
currentEntityClass = currentEntityClass.getSuperclass();
}
return null;
}
private Method getMethodByAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotationClass)) {
return method;
}
}
return null;
}
private Field getFieldByAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(annotationClass)) {
return field;
}
}
return null;
}
}