package ru.hflabs.rcd.lucene.binder; import com.google.common.collect.ImmutableList; import org.springframework.core.convert.converter.Converter; import org.springframework.util.ReflectionUtils; import ru.hflabs.rcd.Directories; import ru.hflabs.rcd.index.IndexedClass; import ru.hflabs.rcd.index.IndexedField; import ru.hflabs.rcd.lucene.IndexBinderTransformer; import ru.hflabs.rcd.model.annotation.Indexed; import ru.hflabs.rcd.service.IServiceFactory; import ru.hflabs.util.core.Holder; import ru.hflabs.util.core.Pair; import ru.hflabs.util.javac.InMemoryJavaFileObject; import ru.hflabs.util.javac.RuntimeCompiler; import ru.hflabs.util.javac.RuntimeCompilerUtil; import ru.hflabs.util.lucene.LuceneBinderTransformer; import ru.hflabs.util.spring.Assert; import ru.hflabs.util.spring.util.ReflectionUtil; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.util.Collection; import java.util.Date; /** * Класс <class>LuceneBinderTransformerFactory</class> реализует фабрику создания <i>runtime</i> сервисов связи поисковой сущности и сущности API * * @author Nazin Alexander * @see IndexBinderTransformer * @see Indexed */ public class LuceneBinderTransformerFactory<E, PK extends Serializable> implements IServiceFactory<IndexBinderTransformer<E, PK>, Class<E>>, Converter<Pair<Class<E>, String>, IndexedField> { /** Кеш сервисов */ private final Holder<Class<E>, IndexBinderTransformer<E, PK>> serviceHolder; /** Кеш полей */ private final Holder<Class<E>, IndexedClass<E>> indexedFieldsHolder; /** Кеш игнорируемых полей */ private final Holder<Class<E>, Collection<Member>> collectionMembersHolder; public LuceneBinderTransformerFactory() { this.serviceHolder = new ServicesHolder(); this.indexedFieldsHolder = new IndexedFieldsHolder(); this.collectionMembersHolder = new CollectionMembersHolder(); } @Override public IndexBinderTransformer<E, PK> retrieveService(Class<E> key) { return serviceHolder.getValue(key); } @Override public void destroyService(Class<E> key, IndexBinderTransformer<E, PK> service) { serviceHolder.removeValue(key); } @Override public IndexedField convert(Pair<Class<E>, String> pair) { IndexedClass<E> indexedClass = retrieveService(pair.first).retrieveIndexedClass(); // Проверяем первичный ключ if (indexedClass.getName().equals(pair.second)) { return indexedClass; } else { // Т.к. поля не уникальны, то ищем первое поле с подходящим именем for (IndexedField indexedField : indexedClass.getFields()) { if (pair.second.equals(indexedField.getName())) { return indexedField; } } // Поля с указанным именем не найдено в целевом классе throw new IllegalArgumentException(String.format("Field with name '%s' not indexed in class '%s'", pair.second, pair.first.getName())); } } /** * Создает новый экземпляр класса * * @param memoryClass исходный класс * @return Возвращает созданный экземпляр класса */ private IndexBinderTransformer<E, PK> createInstance(Class<E> targetClass, Class<IndexBinderTransformer<E, PK>> memoryClass) throws Exception { return memoryClass.getConstructor(IndexedClass.class).newInstance(indexedFieldsHolder.getValue(targetClass)); } private InMemoryJavaFileObject<IndexBinderTransformer<E, PK>> buildInstance(Class<E> targetClass) { final StringWriter writer = new StringWriter(); final PrintWriter out = new PrintWriter(writer); // Определяем пакет класса final String packageName = getClass().getPackage().getName(); out.format("package %s;", packageName).println(); out.println(); // Добавляем необходимые import-ы buildImports(out, targetClass); // Формируем заголовок класса RuntimeCompilerUtil.printPublicClassHeaderTemplate( out, getClass().getName(), String.format("extends %s<%s>", LuceneBinderTransformerTemplate.class.getSimpleName(), targetClass.getSimpleName()) ); // Формирование конструктора buildConstructor(out, RuntimeCompilerUtil.CLASS_NAME_PLACEHOLDER, targetClass); // Формирование методов buildMethod_prepareToSerialize(out, targetClass); buildMethod_reverseConvert(out, targetClass); out.println("}"); out.close(); return RuntimeCompilerUtil.createJavaFileObject( packageName, targetClass.getSimpleName() + LuceneBinderTransformer.class.getSimpleName(), writer.toString() ); } /** * Формирование импортов * * @param out поток вывода * @param targetClass целевой класс */ protected void buildImports(PrintWriter out, Class<E> targetClass) { out.format("import %s;", ru.hflabs.rcd.index.IndexedClass.class.getName()).println(); out.format("import %s;", targetClass.getName()).println(); out.println(); out.format("import %s;", org.apache.lucene.document.Document.class.getName()).println(); out.format("import %s;", org.apache.lucene.document.LongField.class.getName()).println(); out.format("import %s;", org.apache.lucene.document.StoredField.class.getName()).println(); out.format("import %s;", org.apache.lucene.document.StringField.class.getName()).println(); out.format("import %s;", org.apache.lucene.document.TextField.class.getName()).println(); out.format("import %s;", org.apache.lucene.index.IndexableField.class.getName()).println(); out.format("import %s;", ru.hflabs.util.core.FormatUtil.class.getName()).println(); out.format("import %s;", ru.hflabs.util.lucene.LuceneUtil.class.getName()).println(); out.println(); out.format("import %s;", java.util.ArrayList.class.getName()).println(); out.format("import %s;", java.util.List.class.getName()).println(); out.println(); } /** * Формирование конструктора * * @param out поток вывода * @param targetClass целевой класс */ private void buildConstructor(PrintWriter out, String className, Class<E> targetClass) { out.format(" public %s(IndexedClass<%s> indexedClass) {", className, targetClass.getSimpleName()).println(); out.println(" super(indexedClass);"); out.println(" }"); out.println(); } /** * Выполняет построение метода {@link LuceneBinderTransformerTemplate#prepareToSerialize(ru.hflabs.rcd.model.Identifying)} * * @param out поток вывода * @param targetClass целевой класс */ private void buildMethod_prepareToSerialize(PrintWriter out, Class<E> targetClass) { out.println(" @Override"); out.format(" protected %s prepareToSerialize(%s target) {", targetClass.getSimpleName(), targetClass.getSimpleName()).println(); final Collection<Member> collectionMembers = collectionMembersHolder.getValue(targetClass); // Если в объекте есть поля, которые представляют собой коллекцию, то клонируем объект и обнуляем их if (!collectionMembers.isEmpty()) { out.format(" final %s toStore = target.copy();", targetClass.getSimpleName()).println(); for (Member field : collectionMembers) { out.format(" toStore.%s(null);", RuntimeCompilerUtil.set(field)).println(); } out.println(" return toStore;"); } else { out.println(" return target;"); } out.println(" }"); out.println(); } /** * Выполняет построение метода {@link LuceneBinderTransformer#reverseConvert(Object)} * * @param out поток вывода * @param targetClass целевой класс */ private void buildMethod_reverseConvert(PrintWriter out, Class<E> targetClass) { out.println(" @Override"); out.format(" public Document reverseConvert(%s target) {", targetClass.getSimpleName()).println(); out.println(" final Document result = new Document();"); out.println(); // Получаем индексированный класс IndexedClass<E> indexedClass = indexedFieldsHolder.getValue(targetClass); // Добавляем первичный ключ out.format(" // %s", indexedClass.getName()).println(); out.format(" result.add(new StringField(\"%s\", FormatUtil.format(target.%s()), FIELD_STORED));", indexedClass.getName(), RuntimeCompilerUtil.get(indexedClass.getMember())).println(); out.println(); // Список полей поиска поумолчанию out.println(" final List<IndexableField> defaultSearchFields = new ArrayList<IndexableField>();"); // Текущее обрабатываемое поле out.println(" IndexableField currentField;"); // Добавляем индексируемые поля for (IndexedField indexedField : indexedClass.getFields()) { final Member member = indexedField.getMember(); final String memberName = indexedField.getName(); final Class<?> fieldClass = indexedField.getType(); out.format(" // %s", memberName).println(); if (String.class.isAssignableFrom(fieldClass)) { out.format(" currentField = new TextField(\"%s\", FormatUtil.format(target.%s()), FIELD_STORED);", memberName, RuntimeCompilerUtil.get(member)).println(); } else if (Date.class.isAssignableFrom(fieldClass)) { out.format(" currentField = new LongField(\"%s\", LuceneUtil.dateToLong(target.%s()), FIELD_STORED);", memberName, RuntimeCompilerUtil.get(member)).println(); } else if (Number.class.isAssignableFrom(fieldClass)) { out.format(" currentField = new TextField(\"%s\", FormatUtil.format(target.%s()),FIELD_STORED);", memberName, RuntimeCompilerUtil.get(member)).println(); } else if (Enum.class.isAssignableFrom(fieldClass)) { out.format(" currentField = new TextField(\"%s\", FormatUtil.format(target.%s(), false), FIELD_STORED);", memberName, RuntimeCompilerUtil.get(member)).println(); } else { throw new UnsupportedOperationException(String.format("Class '%s' not supported by %s", fieldClass.getSimpleName(), getClass().getName())); } // Добавляем поле в фильтрацию if (indexedField.isStateEnabled(IndexedField.FILTERABLE)) { out.println(" result.add(currentField);"); } // Добавляем в поиск по умолчанию if (indexedField.isStateEnabled(IndexedField.SEARCHABLE)) { out.println(" defaultSearchFields.add(currentField);"); } } // Добавляем поле поиска по умолчанию out.format(" // %s", LuceneBinderTransformer.DEFAULT_SEARCH_FIELD).println(); out.println(" result.add(createDefaultSearchField(defaultSearchFields));"); out.println(); // Добавляем сериализованный объект out.format(" // %s", LuceneBinderTransformer.OBJECT_FIELD).println(); out.format(" result.add(new StoredField(OBJECT_FIELD, LuceneUtil.objectToByte(prepareToSerialize(target), LuceneUtil.KRYO_OBJECT_TO_BYTE_CONVERTER)));").println(); out.println(); out.println(" return result;"); out.println(" }"); out.println(); } /** * Класс <class>ServicesHolder</class> реализует кеш сервисов * * @author Nazin Alexander */ private class ServicesHolder extends Holder<Class<E>, IndexBinderTransformer<E, PK>> { @Override protected IndexBinderTransformer<E, PK> createValue(Class<E> key) { try { return createInstance( key, RuntimeCompiler.compileIfNotFound( buildInstance(key), System.out, Directories.RUNTIME_FOLDER_SOURCE.location, Directories.RUNTIME_FOLDER_COMPILED.location ) ); } catch (Exception ex) { throw new UndeclaredThrowableException(ex); } } } /** * Класс <class>CollectionMembersHolder</class> реализует кеш полей, которые являются коллекциями * * @author Nazin Alexander */ private class CollectionMembersHolder extends Holder<Class<E>, Collection<Member>> { @Override protected Collection<Member> createValue(Class<E> key) { final ImmutableList.Builder<Member> result = ImmutableList.builder(); ReflectionUtil.doWithFields( key, new ReflectionUtils.FieldCallback() { @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { result.add(field); } }, new ReflectionUtils.FieldFilter() { @Override public boolean matches(Field field) { return Collection.class.isAssignableFrom(field.getType()); } } ); return result.build(); } } /** * Класс <class>IndexedFieldsHolder</class> реализует кеш полей, которые присутствуют в аннотации {@link Indexed} * * @author Nazin Alexander */ private class IndexedFieldsHolder extends Holder<Class<E>, IndexedClass<E>> { /** * Создает состояние индексируемого поля по аннотации * * @param annotation аннотация индексирования * @return Возвращает состояние индексируемого поля */ private int createStates(Indexed.Field annotation) { int state = 0; state = state | (annotation.search() ? IndexedField.SEARCHABLE : 0); state = state | (annotation.filter() ? IndexedField.FILTERABLE : 0); state = state | (annotation.sort() ? IndexedField.SORTABLE : 0); return state; } /** * Выполняет формирование индексированного поля основываясь на указанной аннотации * * @param targetClass целевой класс * @param annotation целевая аннотация индексации * @return Возвращает индексированное поле */ private IndexedField createIndexedField(Class<E> targetClass, Indexed.Field annotation) { final int fieldState = createStates(annotation); final Field field = ReflectionUtil.findField(targetClass, annotation.value()); if (field == null) { try { Method method = ReflectionUtil.findGetter(targetClass, annotation.value()); String fieldName = ReflectionUtil.extractFieldName(method); return new IndexedField.ByMethod(fieldState, fieldName, method); } catch (NoSuchMethodException ex) { throw new IllegalArgumentException(String.format("Can't find field or method with name '%s' in class '%s'", annotation.value(), targetClass.getName())); } } else { return new IndexedField.ByField(fieldState, field); } } @Override protected IndexedClass<E> createValue(Class<E> key) { // Получаем аннотацию индексации final Indexed indexed = key.getAnnotation(Indexed.class); Assert.notNull(indexed, String.format("Class '%s' not contains %s annotation", key.getName(), Indexed.class.getSimpleName())); // Первичный ключ Field primaryKey = ReflectionUtil.findField(key, indexed.id()); Assert.notNull(primaryKey, String.format("Can't find field with name '%s' in class '%s'", indexed.id(), key.getName())); // Формируем коллекцию индексированных полей final ImmutableList.Builder<IndexedField> result = ImmutableList.builder(); for (Indexed.Field annotation : indexed.fields()) { IndexedField indexedField = createIndexedField(key, annotation); result.add(indexedField); for (String alias : annotation.alias()) { result.add(new IndexedField.ByAlias(alias, indexedField)); } } // Формируем индексированный класс return new IndexedClass<E>(key, primaryKey, result.build()); } } }