package ru.hflabs.rcd.task.repository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import lombok.AccessLevel;
import lombok.Setter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import ru.hflabs.rcd.exception.constraint.task.IllegalCronSyntaxException;
import ru.hflabs.rcd.exception.search.UnknownTaskDefinitionException;
import ru.hflabs.rcd.model.definition.ModelDefinition;
import ru.hflabs.rcd.model.definition.ModelFieldDefinition;
import ru.hflabs.rcd.model.task.TaskDefinition;
import ru.hflabs.rcd.model.task.TaskDescriptor;
import ru.hflabs.rcd.model.task.TaskParameterDefinition;
import ru.hflabs.rcd.service.IServiceFactory;
import ru.hflabs.rcd.service.ITaskDefinitionRepository;
import ru.hflabs.util.core.collection.IteratorUtil;
import ru.hflabs.util.spring.Assert;
import ru.hflabs.util.spring.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Класс <class>TaskDefinitionRepository</class> реализует сервис работы с репозиторием задач, объявленных в контексте приложения
*
* @author Nazin Alexander
*/
@Setter
public class TaskDefinitionRepository implements ITaskDefinitionRepository, BeanFactoryAware, InitializingBean, DisposableBean {
/** Пустой контекст триггера. Используется для вычисления времени следующего запуска задачи */
private static final TriggerContext EMPTY_TRIGGER_CONTEXT = new SimpleTriggerContext();
/** Фабрика создания классов */
@Setter(AccessLevel.NONE)
private ListableBeanFactory beanFactory;
/** Сервис конвертации объектов */
private ObjectMapper objectMapper;
/** Фабрика описания моделей */
private IServiceFactory<ModelDefinition, Class<?>> modelDefinitionFactory;
/** Коллекция поддерживаемых дескрипторов */
private Map<String, TaskDefinition> definitions;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ListableBeanFactory) {
this.beanFactory = (ListableBeanFactory) beanFactory;
} else {
throw new FactoryBeanNotInitializedException(String.format("BeanFactory must be instance of '%s'", ListableBeanFactory.class.getSimpleName()));
}
}
@Override
public Class<TaskDefinition> retrieveTargetClass() {
return TaskDefinition.class;
}
@Override
public Integer totalCount() {
return definitions.size();
}
@Override
public List<TaskDefinition> getAll() {
return ImmutableList.copyOf(definitions.values());
}
@Override
public Iterator<List<TaskDefinition>> iterateAll(int fetchSize, int cacheSize) {
return IteratorUtil.toPageIterator(getAll().iterator(), fetchSize);
}
@Override
public TaskDefinition findUniqueByNamedPath(String path, boolean quietly) {
Assert.isTrue(StringUtils.hasText(path), "Task descriptor name must not be NULL or EMPTY");
TaskDefinition definition = definitions.get(path);
if (definition == null && !quietly) {
throw new UnknownTaskDefinitionException(path);
}
return definition;
}
@Override
public <T extends Map<String, Object>> T convertTaskParameters(Class<T> parametersClass, Map<String, Object> parameters) {
Assert.notNull(parametersClass, "Task parameters class must not be NULL");
return objectMapper.convertValue(parameters, parametersClass);
}
@Override
public Map<String, Object> convertTaskResults(Map<String, Object> results) {
return objectMapper.convertValue(results, new TypeReference<Map<String, Object>>() {
});
}
@Override
public TaskDescriptor populateParameters(TaskDescriptor descriptor) {
Assert.notNull(descriptor, "Task descriptor must not be NULL");
TaskDefinition definition = findUniqueByNamedPath(descriptor.getName(), false);
{
descriptor.setName(definition.getId());
descriptor.setParameters(
convertTaskParameters(
definition.getParametersClass(),
descriptor.getParameters() != null ? descriptor.getParameters() : definition.getDefaultParameters())
);
}
return descriptor;
}
@Override
public TaskDescriptor populateScheduleDate(TaskDescriptor descriptor) {
Assert.notNull(descriptor, "Task descriptor must not be NULL");
String cronExpression = descriptor.getCron();
if (StringUtils.hasText(cronExpression)) {
try {
Trigger trigger = new CronTrigger(cronExpression);
try {
descriptor.setNextScheduledDate(trigger.nextExecutionTime(EMPTY_TRIGGER_CONTEXT));
} catch (IllegalArgumentException ex) {
descriptor.setNextScheduledDate(null);
}
} catch (IllegalArgumentException ex) {
throw new IllegalCronSyntaxException(ex);
}
} else {
descriptor.setNextScheduledDate(null);
}
return descriptor;
}
@Override
public TaskDescriptor populate(TaskDescriptor descriptor) {
descriptor = populateParameters(descriptor);
descriptor = populateScheduleDate(descriptor);
return descriptor;
}
@Override
public void afterPropertiesSet() throws Exception {
// Формируем описание дескриптора задачи
ModelDefinition taskDescriptorModel = modelDefinitionFactory.retrieveService(TaskDescriptor.class);
// Формируем описание для каждой задачи, которая объявлена в контексте
ImmutableMap.Builder<String, TaskDefinition> result = ImmutableMap.builder();
for (TaskDefinition definition : beanFactory.getBeansOfType(TaskDefinition.class, true, true).values()) {
// Формируем параметры задачи по умолчанию
final Map<String, Object> defaultParameters = Maps.newLinkedHashMap();
ReflectionUtil.doWithFields(
definition.getParametersClass(),
new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
TaskParameterDefinition<?> defaultParameter = TaskParameterDefinition.class.cast(ReflectionUtil.get(field, null));
defaultParameters.put(defaultParameter.name, defaultParameter.value);
}
},
new ReflectionUtils.FieldFilter() {
@Override
public boolean matches(Field field) {
return ReflectionUtil.isPublicStaticFinal(field) && TaskParameterDefinition.class.isAssignableFrom(field.getType());
}
}
);
definition.setDefaultParameters(defaultParameters);
// Формируем описание параметров
ImmutableMap.Builder<String, ModelFieldDefinition> fieldDefinitions = ImmutableMap.builder();
for (Map.Entry<String, ModelFieldDefinition> entry : modelDefinitionFactory.retrieveService(definition.getParametersClass()).getFields().entrySet()) {
fieldDefinitions.put(
String.format("%s.%s", TaskDescriptor.PARAMETERS, entry.getKey()),
entry.getValue()
);
}
// Добавляем поля дескриптора
fieldDefinitions.putAll(taskDescriptorModel.getFields());
definition.setFields(fieldDefinitions.build());
result.put(definition.getId(), definition);
}
definitions = result.build();
}
@Override
public void destroy() throws Exception {
definitions = null;
}
}