All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threewks.thundr.search.gae.meta.SearchMetadata Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/*
 * This file is a component of thundr, a software library from 3wks.
 * Read more: http://3wks.github.io/thundr/
 * Copyright (C) 2015 3wks, 
 *
 * 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.threewks.thundr.search.gae.meta;

import com.threewks.thundr.search.SearchException;
import com.threewks.thundr.search.SearchId;
import com.threewks.thundr.search.SearchIndex;
import com.threewks.thundr.search.gae.GaeSearchService;
import com.threewks.thundr.search.gae.IdGaeSearchService;
import jodd.util.ReflectUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Determines meta data used for the {@link GaeSearchService} and {@link IdGaeSearchService} for a particular class type.
 * 
 * @param 
 * @param 
 */
public class SearchMetadata {
	private static final Pattern FieldNamePattern = Pattern.compile("[a-zA-Z][\\w-]*");
	private Class type;
	private Class keyType;
	private Accessor idAccessor;
	private Map> accessors = new LinkedHashMap<>();
	private Map encodedFieldNames = new HashMap<>();
	private IndexTypeLookup indexTypeLookup;

	public SearchMetadata(Class type, IndexTypeLookup indexTypeLookup) {
		this.indexTypeLookup = indexTypeLookup;
		this.type = type;
		findMethodAccessors(type);
		findFieldAccessors(type);
		if (idAccessor == null) {
			throw new SearchException("No id found for %s - make sure @%s is on one field or method", type.getSimpleName(), SearchId.class.getSimpleName());
		}
		this.keyType = idAccessor.getType();
	}

	public SearchMetadata(Class type, Class keyType, IndexTypeLookup indexTypeLookup) {
		this.indexTypeLookup = indexTypeLookup;
		this.type = type;
		this.keyType = keyType;
		findMethodAccessors(type);
		findFieldAccessors(type);
	}

	public Class getKeyType() {
		return keyType;
	}

	public Class getType() {
		return type;
	}

	public K getId(T t) {
		return idAccessor.get(t);
	}

	public Map getData(T t) {
		Map result = new HashMap();
		for (Accessor accessor : accessors.values()) {
			result.put(accessor.getEncodedName(), accessor.get(t));
		}
		return result;
	}

	public String getEncodedFieldName(String field) {
		Accessor accessor = accessors.get(field);
		return accessor == null ? null : accessor.getEncodedName();
	}

	public IndexType getIndexType(String field) {
		Accessor accessor = accessors.get(field);
		if (accessor == null) {
			throw new SearchException("There is no field or getter marked with @%s for %s.%s - make sure it is indexed and accessible", SearchIndex.class.getSimpleName(), type.getSimpleName(), field);
		}
		return accessor.getIndexType();
	}

	public String getDecodedFieldName(String encodedFieldName) {
		return encodedFieldNames.get(encodedFieldName);
	}

	public Class getFieldType(String field) {
		Accessor accessor = accessors.get(field);
		return accessor == null ? null : accessor.getType();
	}

	private String determineName(Method method) {
		SearchIndex annotation = method.getAnnotation(SearchIndex.class);
		String name = annotation == null ? null : annotation.value();
		if (StringUtils.isBlank(name)) {
			name = method.getName();
			if (name.startsWith("is")) {
				name = WordUtils.uncapitalize(name.substring(2));
			} else if (name.startsWith("get")) {
				name = WordUtils.uncapitalize(name.substring(3));
			}
		}
		return name;
	}

	private String determineName(Field field) {
		SearchIndex annotation = field.getAnnotation(SearchIndex.class);
		return annotation == null || StringUtils.isBlank(annotation.value()) ? field.getName() : annotation.value();
	}

	public boolean hasIndexableFields() {
		return !accessors.isEmpty();
	}

	/**
	 * Ensure the field name matches the requirements of the search interface
	 * 
	 * @param fieldName
	 * @return
	 */
	protected String encodeFieldName(String fieldName) {
		// From the docs - They must start with a letter and can contain letters, digits, or underscore
		String encoded = fieldName.replaceAll("[^\\w-]", "-");
		encoded = encoded.replaceAll("-+", "-");
		encoded = encoded.replaceAll("[^a-zA-Z]*([a-zA-Z]+)([\\w-]*)", "$1$2");
		if (!FieldNamePattern.matcher(encoded).matches()) {
			throw new SearchException(
					"The field name '%s' cannot be used - it could not be encoded into an acceptable representation for the text search api. Make sure it has at least one letter. The encoded result was '%s'",
					fieldName, encoded);
		}
		return encoded;
	}

	private void findFieldAccessors(Class type) {
		// TODO - v3 - Remove Jodd dependencies in favour of ClassIntrospector and MethodIntrospector
		for (Field field : ReflectUtil.getSupportedFields(type)) {
			if (field.isAnnotationPresent(SearchId.class)) {
				String name = determineName(field);
				String encodedName = encodeFieldName(name);
				idAccessor = new FieldAccessor(type, field, name, encodedName, IndexType.Identifier);
				encodedFieldNames.put(encodedName, name);
				accessors.put(name, idAccessor);
			}
			if (field.isAnnotationPresent(SearchIndex.class)) {
				String name = determineName(field);
				String encodedNamed = encodeFieldName(name);
				IndexType indexType = field.getAnnotation(SearchIndex.class).as();
				if (indexType == IndexType.Automatic) {
					indexType = indexTypeLookup.inferIndexType(field.getGenericType());
				}
				accessors.put(name, new FieldAccessor(type, field, name, encodedNamed, indexType));
				encodedFieldNames.put(encodedNamed, name);
			}
		}
	}

	private void findMethodAccessors(Class type) {
		for (Method method : ReflectUtil.getSupportedMethods(type)) {
			if (method.isAnnotationPresent(SearchId.class)) {
				String name = determineName(method);
				String encodedName = encodeFieldName(name);
				idAccessor = new MethodAccessor(type, method, name, encodedName, IndexType.Identifier);
				encodedFieldNames.put(encodedName, name);
				accessors.put(name, idAccessor);
			}
			if (method.isAnnotationPresent(SearchIndex.class)) {
				String name = determineName(method);
				String encodedName = encodeFieldName(name);
				IndexType indexType = method.getAnnotation(SearchIndex.class).as();
				if (indexType == IndexType.Automatic) {
					indexType = indexTypeLookup.inferIndexType(method.getReturnType());
				}
				accessors.put(name, new MethodAccessor(type, method, name, encodedName, indexType));
				encodedFieldNames.put(encodedName, name);
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy