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

de.quantummaid.httpmaid.usecases.UseCasesModule Maven / Gradle / Ivy

/*
 * Copyright (c) 2020 Richard Hauswald - https://quantummaid.de/.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 de.quantummaid.httpmaid.usecases;

import de.quantummaid.httpmaid.CoreModule;
import de.quantummaid.httpmaid.chains.*;
import de.quantummaid.httpmaid.closing.ClosingActions;
import de.quantummaid.httpmaid.generator.GenerationCondition;
import de.quantummaid.httpmaid.generator.Generator;
import de.quantummaid.httpmaid.handler.distribution.DistributableHandler;
import de.quantummaid.httpmaid.handler.distribution.HandlerDistributors;
import de.quantummaid.httpmaid.marshalling.MarshallingModule;
import de.quantummaid.httpmaid.startupchecks.StartupChecks;
import de.quantummaid.httpmaid.usecases.eventfactories.EventFactory;
import de.quantummaid.httpmaid.usecases.eventfactories.enriching.Event;
import de.quantummaid.httpmaid.usecases.eventfactories.enriching.PerEventEnrichers;
import de.quantummaid.httpmaid.usecases.eventfactories.enriching.enrichers.PathParameterEnricher;
import de.quantummaid.httpmaid.usecases.eventfactories.extraction.PerEventExtractors;
import de.quantummaid.httpmaid.usecases.eventfactories.extraction.ResponseMapExtractor;
import de.quantummaid.httpmaid.usecases.mapmaid.ReturnValueSerializer;
import de.quantummaid.httpmaid.websockets.broadcast.Broadcasters;
import de.quantummaid.injectmaid.api.InjectorConfiguration;
import de.quantummaid.mapmaid.MapMaid;
import de.quantummaid.mapmaid.builder.recipes.Recipe;
import de.quantummaid.mapmaid.mapper.deserialization.validation.AggregatedValidationException;
import de.quantummaid.reflectmaid.GenericType;
import de.quantummaid.reflectmaid.ReflectMaid;
import de.quantummaid.reflectmaid.resolvedtype.ResolvedType;
import de.quantummaid.usecasemaid.RoutingTarget;
import de.quantummaid.usecasemaid.UseCaseMaid;
import de.quantummaid.usecasemaid.UseCaseMaidBuilder;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

import java.util.*;
import java.util.function.Consumer;

import static de.quantummaid.httpmaid.CoreModule.REFLECT_MAID;
import static de.quantummaid.httpmaid.HttpMaidChainKeys.RESPONSE_BODY_OBJECT;
import static de.quantummaid.httpmaid.HttpMaidChains.*;
import static de.quantummaid.httpmaid.chains.ChainName.chainName;
import static de.quantummaid.httpmaid.chains.MetaDataKey.metaDataKey;
import static de.quantummaid.httpmaid.chains.rules.Drop.drop;
import static de.quantummaid.httpmaid.chains.rules.Jump.jumpTo;
import static de.quantummaid.httpmaid.closing.ClosingActions.CLOSING_ACTIONS;
import static de.quantummaid.httpmaid.generator.Generator.generator;
import static de.quantummaid.httpmaid.generator.Generators.generators;
import static de.quantummaid.httpmaid.handler.distribution.DistributableHandler.distributableHandler;
import static de.quantummaid.httpmaid.handler.distribution.HandlerDistributors.HANDLER_DISTRIBUTORS;
import static de.quantummaid.httpmaid.marshalling.MarshallingModule.emptyMarshallingModule;
import static de.quantummaid.httpmaid.serialization.Serializer.SERIALIZER;
import static de.quantummaid.httpmaid.startupchecks.StartupChecks.STARTUP_CHECKS;
import static de.quantummaid.httpmaid.usecases.DetermineRoutingTargetProcessor.determineRoutingTargetProcessor;
import static de.quantummaid.httpmaid.usecases.RegisterSerializerProcessor.registerSerializerProcessor;
import static de.quantummaid.httpmaid.usecases.eventfactories.GenericEventFactory.genericEventFactory;
import static de.quantummaid.httpmaid.usecases.eventfactories.enriching.PerEventEnrichers.perEventEnrichers;
import static de.quantummaid.httpmaid.usecases.eventfactories.enriching.enrichers.PathParameterEnricher.pathParameterEnricher;
import static de.quantummaid.httpmaid.usecases.eventfactories.extraction.PerEventExtractors.perEventExtractors;
import static de.quantummaid.httpmaid.usecases.mapmaid.MapMaidMarshallingMapper.mapMaidMarshallingMapper;
import static de.quantummaid.httpmaid.usecases.mapmaid.MapMaidValidationExceptionMapper.mapMaidValidationExceptionMapper;
import static de.quantummaid.httpmaid.usecases.mapmaid.ReturnValueSerializer.returnValueSerializer;
import static de.quantummaid.httpmaid.usecases.processors.BroadcastingProcessor.broadcastingProcessor;
import static de.quantummaid.httpmaid.usecases.processors.ConstructEventMapProcessor.constructEventMapProcessor;
import static de.quantummaid.httpmaid.usecases.processors.DispatchEventProcessor.dispatchEventProcessor;
import static de.quantummaid.httpmaid.usecases.processors.PerRequestEnrichersProcessor.enrichersProcessor;
import static de.quantummaid.httpmaid.usecases.processors.PerRequestExtractorsProcessor.extractorsProcessor;
import static de.quantummaid.httpmaid.usecases.processors.UnwrapDispatchingExceptionProcessor.unwrapDispatchingExceptionProcessor;
import static de.quantummaid.httpmaid.util.Validators.validateNotNull;
import static de.quantummaid.httpmaid.websockets.broadcast.Broadcasters.BROADCASTERS;
import static de.quantummaid.reflectmaid.GenericType.fromResolvedType;
import static de.quantummaid.reflectmaid.GenericType.genericType;
import static de.quantummaid.usecasemaid.RoutingTarget.routingTarget;
import static de.quantummaid.usecasemaid.UseCaseMaid.aUseCaseMaid;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S1905")
public final class UseCasesModule implements ChainModule {
    public static final MetaDataKey ROUTING_TARGET = metaDataKey("ROUTING_TARGET");
    public static final MetaDataKey EVENT = metaDataKey("EVENT");
    public static final MetaDataKey> RECEIVED_EVENT = metaDataKey("RECEIVED_EVENT");

    public static final ChainName MAP_REQUEST_TO_EVENT = chainName("MAP_REQUEST_TO_EVENT");
    public static final ChainName SUBMIT_EVENT = chainName("SUBMIT_EVENT");
    public static final ChainName MAP_EVENT_TO_RESPONSE = chainName("MAP_EVENT_TO_RESPONSE");
    public static final ChainName EXTERNAL_EVENT = chainName("EXTERNAL_EVENT");

    private static final int DEFAULT_VALIDATION_ERROR_STATUS_CODE = 400;

    private final Map useCaseClasses = new LinkedHashMap<>();
    private final List> routingTargetGenerators = new LinkedList<>();
    private final List mapperConfigurations = new LinkedList<>();
    private final List globalScopedInjectorConfigurations = new LinkedList<>();
    private final List requestScopedInjectorConfigurations = new LinkedList<>();

    private final List> eventTypeGenerators = new LinkedList<>();
    private final Map eventFactories = new HashMap<>();
    private final Map enrichers = new HashMap<>();
    private final Map extractors = new HashMap<>();
    private final List responseMapExtractors = new LinkedList<>();
    private UseCaseMaid useCaseMaid;

    private boolean addAggregatedExceptionHandler = true;
    private int validationErrorStatusCode = DEFAULT_VALIDATION_ERROR_STATUS_CODE;

    public static UseCasesModule useCasesModule() {
        return new UseCasesModule();
    }

    public void addMapperConfiguration(final Recipe recipe) {
        this.mapperConfigurations.add(recipe);
    }

    public void addGlobalScopedInjectorConfiguration(final InjectorConfiguration injectorConfiguration) {
        this.globalScopedInjectorConfigurations.add(injectorConfiguration);
    }

    public void addRequestScopedInjectorConfiguration(final InjectorConfiguration injectorConfiguration) {
        this.requestScopedInjectorConfigurations.add(injectorConfiguration);
    }

    public void addResponseMapExtractor(final ResponseMapExtractor extractor) {
        validateNotNull(extractor, "extractor");
        responseMapExtractors.add(extractor);
    }

    public void addEnricher(final RoutingTarget eventType, final Consumer enricher) {
        enrichers.computeIfAbsent(eventType, x -> perEventEnrichers());
        final PerEventEnrichers perEventEnrichers = enrichers.get(eventType);
        enricher.accept(perEventEnrichers);
    }

    public void addExtractor(final RoutingTarget routingTarget, final Consumer extractor) {
        extractors.computeIfAbsent(routingTarget, x -> perEventExtractors());
        final PerEventExtractors perEventExtractors = extractors.get(routingTarget);
        extractor.accept(perEventExtractors);
    }

    public void doNotAddAggregatedExceptionHandler() {
        this.addAggregatedExceptionHandler = false;
    }

    public void setValidationErrorStatusCode(final int validationErrorStatusCode) {
        this.validationErrorStatusCode = validationErrorStatusCode;
    }

    @Override
    public void init(final MetaData configurationMetaData) {
        final ReflectMaid reflectMaid = configurationMetaData.get(REFLECT_MAID);

        final HandlerDistributors handlerDistributors = configurationMetaData.get(HANDLER_DISTRIBUTORS);
        handlerDistributors.register(handler -> handler.handler() instanceof GenericType, handler -> {
            final GenericType useCaseClass = (GenericType) handler.handler();
            final ResolvedType resolvedUseCaseClass = reflectMaid.resolve(useCaseClass);
            final DistributableHandler followUpHandler = distributableHandler(handler.condition(), routingTarget(resolvedUseCaseClass), handler.perRouteConfigurators());
            return singletonList(followUpHandler);
        });
        handlerDistributors.register(handler -> handler.handler() instanceof Class, handler -> {
            final Class clazz = (Class) handler.handler();
            final GenericType useCaseClass = genericType(clazz);
            final ResolvedType resolvedUseCaseClass = reflectMaid.resolve(useCaseClass);
            final DistributableHandler followUpHandler = distributableHandler(handler.condition(), routingTarget(resolvedUseCaseClass), handler.perRouteConfigurators());
            return singletonList(followUpHandler);
        });
        handlerDistributors.register(handler -> handler.handler() instanceof RoutingTarget, handler -> {
            final RoutingTarget routingTarget = (RoutingTarget) handler.handler();
            useCaseClasses.put(handler.condition(), routingTarget);
            return emptyList();
        });
    }

    @Override
    public List supplyModulesIfNotAlreadyPresent() {
        return List.of(emptyMarshallingModule());
    }

    @Override
    public void configure(final DependencyRegistry dependencyRegistry) {
        final ReflectMaid reflectMaid = dependencyRegistry.getMetaDatum(REFLECT_MAID);
        final UseCaseMaidBuilder useCaseMaidBuilder = aUseCaseMaid(reflectMaid);
        useCaseClasses.forEach((condition, routingTarget) -> {
            useCaseMaidBuilder.invoking(routingTarget);
            final Generator eventTypeGenerator = generator(routingTarget, condition);
            routingTargetGenerators.add(eventTypeGenerator);
            final List pathParameters = condition.pathParameters();
            pathParameters.forEach(name -> {
                final PathParameterEnricher enricher = pathParameterEnricher(name, name);
                addEnricher(routingTarget, perEventEnrichers -> perEventEnrichers.addPathParameterEnricher(enricher));
            });
        });
        mapperConfigurations.forEach(useCaseMaidBuilder::withMapperConfiguration);
        requestScopedInjectorConfigurations.forEach(useCaseMaidBuilder::withInvocationScopedDependencies);
        globalScopedInjectorConfigurations.forEach(useCaseMaidBuilder::withDependencies);
        final Broadcasters broadcasters = dependencyRegistry.getMetaDatum(BROADCASTERS);
        broadcasters.injectionTypes().forEach(injectionType -> useCaseMaidBuilder
                .withMapperConfiguration(builder -> builder.injecting(injectionType)));
        final List messageTypes = broadcasters.messageTypes();
        messageTypes.forEach(resolvedType -> useCaseMaidBuilder
                .withMapperConfiguration(builder -> builder.serializing(fromResolvedType(resolvedType))));
        useCaseMaid = useCaseMaidBuilder.build();
        useCaseClasses.values().forEach(routingTarget -> {
            final Collection parameterNames = useCaseMaid.topLevelParameterNamesFor(routingTarget);
            final EventFactory eventFactory = genericEventFactory(parameterNames);
            eventFactories.put(routingTarget, eventFactory);
        });
        final MapMaid mapMaid = useCaseMaid.mapper();
        final MarshallingModule marshallingModule = dependencyRegistry.getDependency(MarshallingModule.class);
        mapMaidMarshallingMapper().mapMarshalling(mapMaid, marshallingModule);
        final StartupChecks startupChecks = dependencyRegistry.getMetaDatum(STARTUP_CHECKS);
        startupChecks.addStartupCheck(useCaseMaid::runStartupChecks);
        if (addAggregatedExceptionHandler) {
            final CoreModule coreModule = dependencyRegistry.getDependency(CoreModule.class);
            coreModule.addExceptionMapper(AggregatedValidationException.class::isInstance,
                    mapMaidValidationExceptionMapper(validationErrorStatusCode));
        }
    }

    @Override
    public void register(final ChainExtender extender) {
        final ClosingActions closingActions = extender.getMetaDatum(CLOSING_ACTIONS);
        closingActions.addClosingAction(() -> useCaseMaid.instantiator().close());

        final MapMaid mapMaid = useCaseMaid.mapper();
        final ReturnValueSerializer returnValueSerializer = returnValueSerializer(mapMaid);
        extender.appendProcessor(INIT, registerSerializerProcessor(returnValueSerializer));
        extender.addMetaDatum(SERIALIZER, returnValueSerializer);

        extender.appendProcessor(DETERMINE_HANDLER, determineRoutingTargetProcessor(generators(routingTargetGenerators)));
        extender.routeIfSet(PREPARE_RESPONSE, jumpTo(MAP_REQUEST_TO_EVENT), ROUTING_TARGET);

        extender.createChain(MAP_REQUEST_TO_EVENT, jumpTo(SUBMIT_EVENT), jumpTo(EXCEPTION_OCCURRED));
        extender.appendProcessor(MAP_REQUEST_TO_EVENT, constructEventMapProcessor(eventFactories));
        extender.appendProcessor(MAP_REQUEST_TO_EVENT, enrichersProcessor(enrichers));

        final Broadcasters broadcasters = extender.getMetaDatum(BROADCASTERS);
        extender.appendProcessor(MAP_REQUEST_TO_EVENT, broadcastingProcessor(broadcasters));

        extender.createChain(SUBMIT_EVENT, jumpTo(MAP_EVENT_TO_RESPONSE), jumpTo(EXCEPTION_OCCURRED));
        extender.appendProcessor(SUBMIT_EVENT, dispatchEventProcessor(useCaseMaid));

        extender.createChain(MAP_EVENT_TO_RESPONSE, jumpTo(POST_INVOKE), jumpTo(EXCEPTION_OCCURRED));
        responseMapExtractors.forEach(extractor -> extender.appendProcessor(MAP_EVENT_TO_RESPONSE, extractor));
        extender.appendProcessor(MAP_EVENT_TO_RESPONSE, extractorsProcessor(extractors));

        extender.appendProcessor(MAP_EVENT_TO_RESPONSE, metaData -> {
            final Object map = metaData.get(RECEIVED_EVENT).orElseGet(HashMap::new);
            metaData.set(RESPONSE_BODY_OBJECT, map);
        });

        extender.appendProcessor(PREPARE_EXCEPTION_RESPONSE, unwrapDispatchingExceptionProcessor());
        extender.createChain(EXTERNAL_EVENT, drop(), jumpTo(EXCEPTION_OCCURRED));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy