Загрузка данных


package io.perfeccionista.framework;

import io.perfeccionista.framework.service.ServiceConfigurationManager;
import io.perfeccionista.framework.service.ConfiguredServiceHolder;
import io.perfeccionista.framework.service.DefaultServiceConfiguration;
import io.perfeccionista.framework.service.DefaultServiceOrder;
import io.perfeccionista.framework.service.Service;
import io.perfeccionista.framework.service.ServiceConfiguration;
import io.perfeccionista.framework.utils.FileUtils;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

import static io.perfeccionista.framework.Environment.PERFECCIONISTA_PROPERTIES_FILE;
import static io.perfeccionista.framework.utils.PackageUtils.validatePackageSet;
import static io.perfeccionista.framework.utils.ReflectionUtilsForClasses.findAllClasses;
import static io.perfeccionista.framework.utils.ReflectionUtilsForClasses.newInstance;

public class DefaultEnvironmentConfiguration implements EnvironmentConfiguration {

    protected static final String SERVICE_SCAN_PACKAGES_PROPERTY = "perfeccionista.services.scanPackages";

    protected Map<Class<? extends Service>, ConfiguredServiceHolder> serviceConfigurationsToAddOrOverride = new HashMap<>();

    protected static volatile boolean cacheReady = false;
    protected static Map<Class<? extends Service>, ConfiguredServiceHolder> cachedServiceConfigurations = new HashMap<>();
    protected static Properties perfeccionistaProperties;
    protected static Properties systemProperties;

    @Override
    public void addOrOverrideServiceConfiguration(@NotNull ConfiguredServiceHolder configuredServiceHolder) {
        this.serviceConfigurationsToAddOrOverride.put(configuredServiceHolder.getServiceClass(), configuredServiceHolder);
    }

    @Override
    public @NotNull ServiceConfigurationManager getServiceConfigurations() {
        Map<Class<? extends Service>, ConfiguredServiceHolder> services = new HashMap<>();
        synchronized (DefaultEnvironmentConfiguration.class) {
            if (!cacheReady) {
                readProperties();
                findAllServices().forEach(serviceConfiguration ->
                        cachedServiceConfigurations.put(serviceConfiguration.getServiceClass(), serviceConfiguration));
                cacheReady = true;
            }
        }
        services.putAll(cachedServiceConfigurations);
        services.putAll(serviceConfigurationsToAddOrOverride);
        return ServiceConfigurationManager.of(services);
    }

    protected void readProperties() {
        perfeccionistaProperties = FileUtils.readOptionalPropertyFileFromClasspath(PERFECCIONISTA_PROPERTIES_FILE)
                .orElse(new Properties());
        systemProperties = System.getProperties();
    }

    protected @NotNull Set<ConfiguredServiceHolder> findAllServices() {
        Set<Class<? extends Service>> serviceClasses = findAllClasses(getServiceScanPackages(), Service.class).stream()
                .filter(serviceClass -> !Modifier.isAbstract(serviceClass.getModifiers())
                        && !serviceClass.isInterface()
                        && !serviceClass.isEnum())
                .collect(Collectors.toSet());
        Map<Class<? extends Service>, ConfiguredServiceHolder> servicesWithDefaultConfiguration = new HashMap<>();
        for (Class<? extends Service> serviceClass : serviceClasses) {
            ConfiguredServiceHolder serviceHolder = resolveDefaultServiceConfiguration(serviceClass);
            servicesWithDefaultConfiguration.put(serviceClass, serviceHolder);
        }
        return new HashSet<>(servicesWithDefaultConfiguration.values());
    }

    protected @NotNull Set<String> getServiceScanPackages() {
        // Переменная окружения имеет самый высокий приоритет
        if (systemProperties.containsKey(SERVICE_SCAN_PACKAGES_PROPERTY)) {
            List<String> packages = Arrays.asList(systemProperties.getProperty(SERVICE_SCAN_PACKAGES_PROPERTY).split(","));
            return validatePackageSet(new HashSet<>(packages));
        }
        // Если переменная окружения не задана, то ищем переменную заданную в свойствах проекта
        if (perfeccionistaProperties.containsKey(SERVICE_SCAN_PACKAGES_PROPERTY)) {
            List<String> packages = Arrays.asList(perfeccionistaProperties.getProperty(SERVICE_SCAN_PACKAGES_PROPERTY).split(","));
            return validatePackageSet(new HashSet<>(packages));
        }
        // Если она не задана, то используется базовый набор пакетов для сканирования
        return new HashSet<>(Arrays.asList("io.perfeccionista.framework.value", "io.perfeccionista.framework.datasource", "io.perfeccionista.framework.fixture"));
    }

    protected ConfiguredServiceHolder resolveDefaultServiceConfiguration(Class<? extends Service> serviceClass) {
        ConfiguredServiceHolder configuredServiceHolder = ConfiguredServiceHolder.of(serviceClass, serviceClass);
        DefaultServiceConfiguration defaultConfiguration = serviceClass.getAnnotation(DefaultServiceConfiguration.class);
        if (Objects.nonNull(defaultConfiguration)) {
            Class<? extends ServiceConfiguration> serviceConfigurationClass = defaultConfiguration.value();
            configuredServiceHolder = ConfiguredServiceHolder.of(serviceClass, serviceClass, newInstance(serviceConfigurationClass));
        }
        DefaultServiceOrder defaultOrder = serviceClass.getAnnotation(DefaultServiceOrder.class);
        if (Objects.nonNull(defaultOrder)) {
            configuredServiceHolder.setOrder(defaultOrder.value());
        }
        return configuredServiceHolder;
    }

}


есть еще такое, но он помечен как неиспользуемый в ide
public class PlaywrightEnvironmentConfiguration extends DefaultEnvironmentConfiguration {

  @Override
  public @NotNull ServiceConfigurationManager getServiceConfigurations() {
    return ServiceConfigurationManager.of()
        .put(ConfiguredServiceHolder.of(ConfigurationService.class,
            new DefaultConfigurationServiceConfiguration()))
        .put(ConfiguredServiceHolder.of(RestService.class, new Allure2RestServiceConfiguration()).setOrder(-10))
        .put(ConfiguredServiceHolder.of(PlaywrightService.class, new DefaultPlaywrightServiceConfiguration()))
        .put(ConfiguredServiceHolder.of(TimeoutsService.class, new DefaultTimeoutsServiceConfiguration()))
        .put(ConfiguredServiceHolder.of(InvocationService.class, JUnit5InvocationService.class,
            new AllureInvocationServiceConfiguration()));
  }
}

public class Environment {
    private static final Logger logger = LoggerFactory.getLogger(Environment.class);

    public static final String PERFECCIONISTA_PROPERTIES_FILE = "perfeccionista.properties";
    public static final String ENVIRONMENT_ATTACHMENT_NAME = "Environment configuration";

    protected static final InheritableThreadLocal<Environment> INSTANCES = new InheritableThreadLocal<>();
    protected static Deque<Runnable> afterAllHooks = new ArrayDeque<>();

    protected final String testName;
    protected final EnvironmentConfiguration configuration;
    protected final Map<String, Object> relatedObjects = new HashMap<>();
    protected final Map<Class<? extends Service>, Service> services = new LinkedHashMap<>();

    protected TextAttachmentEntry environmentAttachment = TextAttachmentEntry.of(ENVIRONMENT_ATTACHMENT_NAME, "Environment is not initialized");

    public Environment(@NotNull Class<? extends EnvironmentConfiguration> configurationClass,
                       @Nullable String testName) {
        this(newInstance(configurationClass), testName);
    }

    /**
     * Экземпляр {@link Environment} создается во время инициализации
     * Наследники этого класса должны иметь такой же конструктор
     */
    public Environment(@NotNull Class<? extends EnvironmentConfiguration> configurationClass) {
        this(newInstance(configurationClass), null);
    }

    /**
     * Экземпляр {@link Environment} создается во время инициализации
     * Наследники этого класса должны иметь такой же конструктор
     */
    public Environment(@NotNull EnvironmentConfiguration configuration,
                       @Nullable String testName) {
        Preconditions.notNull(configuration, "configuration must not be null");
        this.testName = testName;
        this.configuration = configuration;
    }

    public Environment(@NotNull EnvironmentConfiguration configuration) {
        this(configuration, null);
    }

    public Environment init() {
        if (!this.services.isEmpty()) {
            throw EnvironmentAlreadyInitialized.exception(ENVIRONMENT_ALREADY_INITIALIZED.getMessage());
        }
        logger.debug("Environment configuration initialization [" + this + "]");
        initEnvironment(configuration, testName);
        logger.debug("Environment configuration initialization success [" + this + "]");
        return this;
    }

    public Environment beforeTest() {
        logger.debug("Environment configuration before test task [" + this + "]");
        this.getServices().forEach(Service::beforeTest);
        logger.debug("Environment configuration before test task success [" + this + "]");
        return this;
    }

    public Environment afterTest() {
        logger.debug("Environment configuration after test task [" + this + "]");
        List<Service> services = this.getServices().collect(Collectors.toList());
        Collections.reverse(services);
        services.forEach(Service::afterTest);
        return this;
    }

    public Environment shutdown() {
        logger.debug("Environment shutdown [" + this + "]");
        this.services.clear();
        logger.debug("Environment shutdown success [" + this + "]");
        return this;
    }

    /**
     * Используется для передачи в окружение теста информации о внешних объектах
     * по отношению к тесту. Например, это может быть TestContext или ExtensionContext,
     * если экземпляр Environment создается через Extension или другую сущность,
     * внешнюю, по отношению к тесту.
     * Эта информация может быть использована при инициализации сервисов.
     * Добавление объектов для их использования при инициализации сервисов должно происходить
     * ДО вызова метода Environment.init().
     * @param relatedObject
     * @return
     */
    public Environment addRelatedObject(@NotNull String name, @Nullable Object relatedObject) {
        this.relatedObjects.put(name, relatedObject);
        return this;
    }

    public Map<String, Object> getRelatedObjects() {
        return Collections.unmodifiableMap(relatedObjects);
    }

    /**
     * Регистрируем все экземпляры {@link Service}, которые описаны в
     * конфигурации внутри созданного {@link Environment}.
     * Регистрируемый {@link Service} обязательно должен быть
     * экземпляром класса по которому он регистрируется.
     * @param service регистрируемый экземпляр {@link Service}
     * @return текущий экземпляр {@link Environment}
     */
    public Environment register(@NotNull Class<? extends Service> serviceClass, @NotNull Service service) {
        Preconditions.notNull(serviceClass, "serviceClass must not be null");
        Preconditions.notNull(service, "service must not be null");
        if (!serviceClass.isInstance(service)) {
            throw new ClassCastException(
                    SERVICE_REGISTER_CLASS_CAST.getMessage(service, serviceClass));
        }
        if (services.containsKey(serviceClass)) {
            throw RegisterDuplicate.exception(SERVICE_REGISTER_BY_CLASS_DUPLICATE.getMessage(service));
        }
        services.put(serviceClass, service);
        return this;
    }

    /**
     * Возвращает экземпляр зарегистрированного {@link Service}
     * по строгому соответствию класса по которому этот {@link Service}
     * был зарегистрирован.
     * @param serviceClass класс, по которому {@link Service} зарегистрирован
     * @param <T>          тип возвращаемого {@link Service}
     * @return Экземпляр сервиса, который был зарегистрирован
     * по указанному классу или ServiceNotFoundException, если по указанному классу
     * не зарегистрировано экземпляров {@link Service}.
     */
    public @NotNull <T extends Service> T getService(@NotNull Class<T> serviceClass) {
        Preconditions.notNull(serviceClass, "serviceClass must not be null");
        if (services.containsKey(serviceClass)) {
            return serviceClass.cast(services.get(serviceClass));
        }
        throw ServiceNotFound.exception(SERVICE_NOT_FOUND.getMessage(serviceClass.getCanonicalName()));
    }

    public @NotNull <T extends Service> Optional<T> getOptionalService(@NotNull Class<T> serviceClass) {
        Preconditions.notNull(serviceClass, "serviceClass must not be null");
        if (services.containsKey(serviceClass)) {
            return Optional.of(serviceClass.cast(services.get(serviceClass)));
        }
        return Optional.empty();
    }

    /**
     * набор зарегистрированных экземпляров {@link Service}
     * @return {@link Stream}
     */
    public Stream<Service> getServices() {
        return services.values().stream();
    }

    /**
     * Возвращает классы всех зарегистрированных {@link Service}
     */
    public Set<Class<? extends Service>> getServiceClasses() {
        return services.keySet();
    }

    /**
     * @return Возвращает используемую {@link Environment} конфигурацию
     */
    public @NotNull EnvironmentConfiguration getEnvironmentConfiguration() {
        return configuration;
    }

    /**
     * @return Возвращает текстовое представление текущей инициализированной конфигурации.
     * Или пустой аттачмент, если на момент запроса Environment еще не был инициализирован
     */
    public @NotNull TextAttachmentEntry getEnvironmentAttachment() {
        return this.environmentAttachment;
    }

    // ThreadLocal

    public static Environment createForCurrentThread(@NotNull Class<? extends EnvironmentConfiguration> configurationClass,
                                                     @Nullable String testName) {
        Preconditions.notNull(configurationClass, "Environment configuration class must not be null");
        Environment environment = new Environment(configurationClass, testName);
        INSTANCES.set(environment);
        return environment;
    }

    /**
     *
     * @param configurationClass
     * @return
     */
    public static Environment createForCurrentThread(@NotNull Class<? extends EnvironmentConfiguration> configurationClass) {
        Preconditions.notNull(configurationClass, "Environment configuration class must not be null");
        Environment environment = new Environment(configurationClass);
        INSTANCES.set(environment);
        return environment;
    }

    public static Environment createForCurrentThread(@NotNull EnvironmentConfiguration configuration,
                                                     @Nullable String testName) {
        Preconditions.notNull(configuration, "Environment configuration instance must not be null");
        Environment environment = new Environment(configuration, testName);
        INSTANCES.set(environment);
        return environment;
    }

    /**
     *
     * @param configuration
     * @return
     */
    public static Environment createForCurrentThread(@NotNull EnvironmentConfiguration configuration) {
        Preconditions.notNull(configuration, "Environment configuration instance must not be null");
        Environment environment = new Environment(configuration);
        INSTANCES.set(environment);
        return environment;
    }

    /**
     */
    public static boolean existForCurrentThread() {
        return Objects.nonNull(INSTANCES.get());
    }

    /**
     * {@link Environment} для текущего {@link Thread}.
     * В противном случае, выбрасывает исключение {@link io.perfeccionista.framework.exceptions.EnvironmentNotInitialized}
     */
    public static Environment getForCurrentThread() {
        return Optional.ofNullable(INSTANCES.get())
                .orElseThrow(() -> EnvironmentNotInitialized.exception(ENVIRONMENT_NOT_INITIALIZED.getMessage()));
    }

    /**
     * Привязывает этот экземпляр {@link Environment} к потоку,
     * в котором он создан {@link Thread}
     */
    public static Environment setForCurrentThread(@NotNull Environment environment) {
        Preconditions.notNull(environment, "Environment instance must not be null");
        INSTANCES.set(environment);
        return environment;
    }

    public static void finalizeForCurrentThread() {
        getForCurrentThread()
                .shutdown()
                .removeForCurrentThread();
    }

    /**
     * Отвязывает этот экземпляр {@link Environment} от потока,
     * в котором он создан {@link Thread}
     */
    public Environment removeForCurrentThread() {
        INSTANCES.remove();
        return this;
    }

    // After all actions

    public static void addAfterAllHook(@NotNull Runnable afterAllHook) {
        afterAllHooks.add(afterAllHook);
    }

    public static void executeAfterAllHooks() {
        afterAllHooks.forEach(Runnable::run);
    }

    // Check and Initialization

//    /**
//     * TODO: На этот метод нужны тесты
//     * Этот метод выполняет проверки валидности конфигурации для инициализации {@link Environment}
//     */
//    protected void checkEnvironmentConfiguration(@NotNull EnvironmentConfiguration environmentConfiguration) {
//        Preconditions.notNull(environmentConfiguration, "environmentConfiguration must not be null");
//        // TODO: Добавить необходимые проверки валидности
//    }

    /**
     * Этот метод отвечает за инициализацию всех провайдеров, описанных в конфигурации
     * @param environmentConfiguration конфигурация
     */
    protected void initEnvironment(@NotNull EnvironmentConfiguration environmentConfiguration,
                                   @Nullable String testName) {
        Preconditions.notNull(environmentConfiguration, "environmentConfiguration must not be null");
        EnvironmentLogger environmentLogger = EnvironmentLogger.of(environmentConfiguration, testName);
        environmentLogger.start();

        environmentConfiguration.getServiceConfigurations()
                .stream()
                .sorted(Comparator.comparingInt(ConfiguredServiceHolder::getOrder))
                .forEachOrdered(serviceHolder -> {
                    long startTime = System.nanoTime();
                    if (serviceHolder.isEnabled()) {
                        if (serviceHolder.isConfigured()) {
                            initService(serviceHolder.getServiceClass(),
                                    serviceHolder.getServiceImplementation(),
                                    serviceHolder.getServiceConfiguration());
                        } else {
                            initService(serviceHolder.getServiceClass(),
                                    serviceHolder.getServiceImplementation());
                        }
                    }
                    environmentLogger.addServiceRecord(serviceHolder, startTime, System.nanoTime());
                });

        environmentLogger.finish();
        this.environmentAttachment = TextAttachmentEntry.of(ENVIRONMENT_ATTACHMENT_NAME, environmentLogger.toString());
        environmentConfiguration.getEnvironmentAttachmentProcessor()
                .process(this.environmentAttachment);
    }

    protected void initEnvironment(@NotNull EnvironmentConfiguration environmentConfiguration) {
        initEnvironment(environmentConfiguration, null);
    }

    protected void initService(@NotNull Class<? extends Service> serviceClass,
                               @NotNull Class<? extends Service> serviceImplementation) {
        Preconditions.notNull(serviceClass, "serviceClass must not be null");
        Preconditions.notNull(serviceImplementation, "serviceImplementation must not be null");
        Service serviceInstance = newInstance(serviceImplementation);
        serviceInstance.init(this);
        register(serviceClass, serviceInstance);
    }

    protected void initService(@NotNull Class<? extends Service> serviceClass,
                               @NotNull Class<? extends Service> serviceImplementation,
                               @NotNull ServiceConfiguration serviceConfiguration) {
        Preconditions.notNull(serviceClass, "serviceClass must not be null");
        Preconditions.notNull(serviceImplementation, "serviceImplementation must not be null");
        Preconditions.notNull(serviceConfiguration, "serviceConfiguration must not be null");
        try {
            Service serviceInstance = newInstance(serviceImplementation);
            serviceInstance.init(this, serviceConfiguration);
            register(serviceClass, serviceInstance);
        } catch (Exception ex) {
           throw EnvironmentNotInitialized.exception(ENVIRONMENT_SERVICE_INITIALIZING_FAILED.getMessage(serviceImplementation.getCanonicalName()), ex);
        }
    }

}