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

com.alon.spring.crud.api.projection.ProjectionService Maven / Gradle / Ivy

Go to download

Fornece implementação básica e expansível para criação API's CRUD com Spring Boot e Spring Data JPA.

There is a newer version: 1.0.9
Show newest version
package com.alon.spring.crud.api.projection;

import com.alon.spring.crud.api.controller.output.OutputPage;
import com.alon.spring.crud.domain.model.BaseEntity;
import com.alon.spring.crud.domain.service.exception.ProjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Service
public class ProjectionService {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(ProjectionService.class);
    
    public static final String NOP_PROJECTION = "no-operation-projection";
    
    private ApplicationContext applicationContext;
    
    private RepresentationService representationService;
    
    private final Map projections;
    
    private final Map> representationsCache = new HashMap<>();
    
    public ProjectionService(ApplicationContext applicationContext, RepresentationService representationService) {
        this.applicationContext = applicationContext;
        this.representationService = representationService;
        this.projections = this.applicationContext.getBeansOfType(Projector.class);
    }

    public  O project(String projectionName, I input) {
        if (projectionName == null || projectionName.equals(NOP_PROJECTION))
            return (O) input;

        try {
            Projector projector = getProjector(projectionName);
            return (O) projector.project(input);
        } catch (ProjectionException e) {
            throw e;
        } catch (Exception e) {
            String message = String.format(
                    "Error projecting entity %s with projector '%s'", 
                    input.getClass().getSimpleName(), 
                    projectionName);
            
            LOGGER.error(message, e);
            throw new ProjectionException(message, e);
        }
    }

    public  Collection project(String projectionName, Collection input) {
        if (projectionName == null || projectionName.equals(NOP_PROJECTION))
            return (Collection) input;

        try {
            Projector projector = getProjector(projectionName);

            return input.stream()
                    .map(projector::project)
                    .collect(Collectors.toCollection(getCollectionFactory(input.getClass())));
        } catch (ProjectionException e) {
            throw e;
        } catch (Exception e) {
            String message = String.format(
                    "Error projecting collection of %s with projector '%s'",
                    extractCollectionElementsType(input),
                    projectionName);

            LOGGER.error(message, e);
            throw new ProjectionException(message, e);
        }
    }

    public  OutputPage project(String projectionName, Page input) {
        try {
            List content = input.getContent();

            if (projectionName == null || projectionName.equals(NOP_PROJECTION)) {
                content = input.getContent();
            } else {
                Projector projector = getProjector(projectionName);

                content = input.getContent().stream()
                        .map(projector::project)
                        .collect(Collectors.toList());
            }

            return OutputPage.of()
                    .page(input.getNumber())
                    .pageSize(input.getNumberOfElements())
                    .totalPages(input.getTotalPages())
                    .totalSize(Long.valueOf(input.getTotalElements()).intValue())
                    .content(content)
                    .build();
        } catch (ProjectionException e) {
            throw e;
        } catch (Exception e) {
            String message = String.format(
                    "Error projecting page with projector '%s'",
                    projectionName);
            
            LOGGER.error(message, e);
            throw new ProjectionException(message, e);
        }
    }
    
    public Set getRequiredExpand(String projectionName) {
        Projector projector = getProjector(projectionName);
        return projector.requiredExpand();
    }
    
    public boolean projectionExists(String projectionName) {
        return projections.containsKey(projectionName);
    }
    
    public List getEntityRepresentations(Class clazz,
            Supplier singleDefaultProjectionSupplier, Supplier collectionDefaultProjectionSupplier) {

        if (representationsCache.containsKey(clazz))
            return representationsCache.get(clazz);
        
        Map projections = getEntityProjections(clazz);

        List representations = projections.entrySet().stream()
                .map(this::getProjectionRepresentation)
                .peek(representation -> resolveDefaultRepresentation(representation,
                        singleDefaultProjectionSupplier, collectionDefaultProjectionSupplier))
                .collect(Collectors.toList());

        representationsCache.put(clazz, representations);
        
        return representations;
    }

    private void resolveDefaultRepresentation(ProjectionRepresentation representation,
            Supplier singleDefaultProjectionSupplier, Supplier collectionDefaultProjectionSupplier) {

        String singleDefaultProjection = singleDefaultProjectionSupplier.get();
        String collectionDefaultProjection = collectionDefaultProjectionSupplier.get();

        if (singleDefaultProjection.equals(representation.projectionName))
            representation.setSingleDefault(true);

        if (collectionDefaultProjection.equals(representation.projectionName))
            representation.setCollectionDefault(true);
    }

    private Projector getProjector(String projectionName) {
        Projector projector = projections.get(projectionName);

        if (projector == null)
            throw new ProjectionException(String.format(
                    "Projection '%s' not found", projectionName));

        return projector;
    }

    private Map getEntityProjections(Class entityType) {
        return projections.keySet().stream()
                .filter(key -> projectorInputTypeIsTheExpected(key, entityType))
                .collect(Collectors.toMap(projectionName -> projectionName, projections::get));
    }

    private ProjectionRepresentation getProjectionRepresentation(Map.Entry entry) {
        Class projectionOutput = getProjectorOutputType(entry.getValue());
        Map representation = representationService.getRepresentationOf(projectionOutput);
        return new ProjectionRepresentation(entry.getKey(), representation);
    }

    private boolean projectorInputTypeIsTheExpected(String projectionName, Class expectedInputType) {
        Projector projector = projections.get(projectionName);
        Type inputType = getProjectorInputType(projector);
        
        if (!(inputType instanceof Class)) // A classe pode usar generics e aqui seria um TypeVariableImpl ao invés de Class
            return false;
        
        return ((Class) inputType).isAssignableFrom(expectedInputType);
    }
    
    private Type getProjectorInputType(Projector projector) {
        ParameterizedType projectorType = getProjectorParameterizedType(projector);
        return projectorType.getActualTypeArguments()[0];
    }
    
    private Class getProjectorOutputType(Projector projector) {
        ParameterizedType projectorType = getProjectorParameterizedType(projector);
        return (Class) projectorType.getActualTypeArguments()[1];
    }
    
    private ParameterizedType getProjectorParameterizedType(Projector projector) {
        Optional projectorTypeOpt = List.of(projector.getClass().getGenericInterfaces())
                .stream()
                .filter(type -> type instanceof ParameterizedType)
                .filter(type -> ((ParameterizedType) type).getRawType().equals(Projector.class))
                .findFirst();
        
        return (ParameterizedType) projectorTypeOpt.get();
    }

    private final String extractCollectionElementsType(Collection collection) {
        ParameterizedType classType = (ParameterizedType) getClass().getGenericInterfaces()[0];
        return ((Class) classType.getActualTypeArguments()[0]).getSimpleName();
    }

    private final  Supplier> getCollectionFactory(Class collectionType) {

        if (List.class.isAssignableFrom(collectionType))
            return ArrayList::new;
        else if (Set.class.isAssignableFrom(collectionType))
            return HashSet::new;

        throw new IllegalArgumentException(
                String.format("Collection type %s is not supported by projections.",
                        collectionType.getSimpleName()));
    }

}