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

com.alon.spring.crud.api.controller.NestedCrudController 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.controller;

import com.alon.spring.crud.api.controller.input.Options;
import com.alon.spring.crud.api.controller.input.OptionsNormalizer;
import com.alon.spring.crud.api.controller.input.SearchInput;
import com.alon.spring.crud.api.controller.input.SearchResolver;
import com.alon.spring.crud.api.controller.input.mapper.InputMapper;
import com.alon.spring.crud.api.controller.input.mapper.ModelMapperInputMapper;
import com.alon.spring.crud.api.projection.ProjectionRepresentation;
import com.alon.spring.crud.api.projection.ProjectionService;
import com.alon.spring.crud.domain.model.BaseEntity;
import com.alon.spring.crud.domain.service.CrudService;
import com.alon.spring.crud.domain.service.NestedCrudService;
import com.alon.spring.crud.domain.service.SearchCriteria;
import com.alon.spring.crud.domain.service.exception.*;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.ResponseEntity.BodyBuilder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.server.ResponseStatusException;

import javax.validation.Valid;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public abstract class NestedCrudController<
        MASTER_ENTITY_ID_TYPE extends Serializable,
        MASTER_ENTITY_TYPE extends BaseEntity,
        MASTER_SERVICE_TYPE extends CrudService,
        NESTED_ENTITY_ID_TYPE extends Serializable,
        NESTED_ENTITY_TYPE extends BaseEntity,
        NESTED_SERVICE_TYPE extends NestedCrudService<
                MASTER_ENTITY_ID_TYPE, MASTER_ENTITY_TYPE,
                NESTED_ENTITY_ID_TYPE, NESTED_ENTITY_TYPE>,
        CREATE_INPUT_TYPE, UPDATE_INPUT_TYPE, SEARCH_INPUT_TYPE extends SearchInput> {

    protected final MASTER_SERVICE_TYPE masterService;
    protected final NESTED_SERVICE_TYPE nestedService;

    @Autowired
    protected ProjectionService projectionService;

    @Autowired
    private OptionsNormalizer optionsNormalizer;

    @Autowired
    private SearchResolver searchResolver;

    protected InputMapper createInputMapper;
    protected InputMapper updateInputMapper;

    protected final boolean disableContentCaching;

    protected Class nestedEntityClass = extractNestedEntityType();

    public NestedCrudController(MASTER_SERVICE_TYPE masterService, NESTED_SERVICE_TYPE nestedService) {
        this.masterService = masterService;
        this.nestedService = nestedService;
        this.createInputMapper = new ModelMapperInputMapper<>(nestedEntityClass);
        this.updateInputMapper = new ModelMapperInputMapper<>(nestedEntityClass);
        this.disableContentCaching = true;
    }

    public NestedCrudController(MASTER_SERVICE_TYPE masterService,
            NESTED_SERVICE_TYPE nestedService, boolean disableContentCaching) {

        this.masterService = masterService;
        this.nestedService = nestedService;
        this.createInputMapper = new ModelMapperInputMapper<>(nestedEntityClass);
        this.updateInputMapper = new ModelMapperInputMapper<>(nestedEntityClass);
        this.disableContentCaching = disableContentCaching;
    }

    protected NestedCrudController(MASTER_SERVICE_TYPE masterService,
            NESTED_SERVICE_TYPE nestedService,
            InputMapper createInputMapper,
            InputMapper updateInputMapper) {

        this(masterService, nestedService, createInputMapper, updateInputMapper, true);
    }

    protected NestedCrudController(MASTER_SERVICE_TYPE masterService,
            NESTED_SERVICE_TYPE nestedService,
            InputMapper createInputMapper,
            InputMapper updateInputMapper,
            boolean disableContentCaching) {

        this.masterService = masterService;
        this.nestedService = nestedService;
        this.createInputMapper = createInputMapper;
        this.updateInputMapper = updateInputMapper;
        this.disableContentCaching = disableContentCaching;
    }

    @ApiOperation(value = "Search nested resources",
            nickname = "Search nested", produces = MediaType.APPLICATION_JSON_VALUE)
    @GetMapping
    public ResponseEntity search(
            SEARCH_INPUT_TYPE search,
            @PathVariable MASTER_ENTITY_ID_TYPE masterId,
            Pageable pageable,
            @Valid Options options,
            ServletWebRequest request
    ) {
        if (disableContentCaching)
            ShallowEtagHeaderFilter.disableContentCaching(request.getRequest());

        optionsNormalizer.normalizeOptions(options,
                this::getCollectionDefaultProjection, this::getCollectionAllowedProjections);

        Specification specification = searchResolver.resolve(search);

        SearchCriteria criteria = SearchCriteria.of()
                .filter(specification)
                .pageable(pageable)
                .expand(options.getExpand())
                .build();

        Collection response;

        try {
            response = nestedService.search(masterId, criteria);
        } catch (NotFoundException ex) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
        }

        try {
            response = projectionService.project(options.getProjection(), response);
        } catch (ProjectionException e) {
            if (optionsNormalizer.projectDefaultOnError(options.getProjection(), this::getCollectionDefaultProjection))
                response = projectionService.project(getCollectionDefaultProjection(), response);
            else
                throw e;
        }

        return buildHttpGETResponseEntity(HttpStatus.OK)
                .body(response);
    }

    @ApiOperation(value = "Read a nested resource",
            nickname = "Read nested", produces = MediaType.APPLICATION_JSON_VALUE)
    @GetMapping("/{nestedId}")
    public ResponseEntity read(
            @PathVariable MASTER_ENTITY_ID_TYPE masterId,
            @PathVariable NESTED_ENTITY_ID_TYPE nestedId,
            @Valid Options options,
            ServletWebRequest request
    ) throws ReadException {
        if (disableContentCaching)
            ShallowEtagHeaderFilter.disableContentCaching(request.getRequest());

        optionsNormalizer.normalizeOptions(options,
                this::getSingleDefaultProjection, this::getSingleAllowedProjections);

        NESTED_ENTITY_TYPE entity;

        try {
            entity = nestedService.read(masterId, nestedId, List.copyOf(options.getExpand()));
        } catch (NotFoundException ex) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
        }

        Object response;

        try {
            response = projectionService.project(options.getProjection(), entity);
        } catch (ProjectionException e) {
            if (optionsNormalizer.projectDefaultOnError(options.getProjection(), this::getSingleDefaultProjection))
                response = projectionService.project(getSingleDefaultProjection(), entity);
            else
                throw e;
        }

        return buildHttpGETResponseEntity(HttpStatus.OK)
                .body(response);
    }

    @ApiOperation(value = "Create a nested resource", nickname = "Create nested",
            consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    @PostMapping
    protected ResponseEntity create(
            @PathVariable MASTER_ENTITY_ID_TYPE masterId,
            @RequestBody @Valid CREATE_INPUT_TYPE input) throws CreateException {

        NESTED_ENTITY_TYPE nestedEntity = createInputMapper.map(input);
        
        nestedEntity = nestedService.create(masterId, nestedEntity);
        
        Object response = projectionService.project(getSingleDefaultProjection(), nestedEntity);

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(response);
        
    }

    @ApiOperation(value = "Update a nested resource", nickname = "Update nested",
            consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    @PutMapping("/{nestedId}")
    public ResponseEntity update(
            @PathVariable MASTER_ENTITY_ID_TYPE masterId,
            @PathVariable NESTED_ENTITY_ID_TYPE nestedId,
            @RequestBody @Valid UPDATE_INPUT_TYPE input) throws UpdateException {

        NESTED_ENTITY_TYPE entity = updateInputMapper.map(input);
        entity.setId(nestedId);

        try {
            entity = nestedService.update(masterId, nestedId, entity);
        } catch (NotFoundException ex) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
        }

        Object response = projectionService.project(getSingleDefaultProjection(), entity);

        return ResponseEntity
                .status(HttpStatus.OK)
                .body(response);
        
    }

    @ApiOperation(value = "Delete a nested resource", nickname = "Delete nested")
    @DeleteMapping("/{nestedId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable MASTER_ENTITY_ID_TYPE masterId,
           @PathVariable NESTED_ENTITY_ID_TYPE nestedId) throws DeleteException {

        try {
            nestedService.delete(masterId, nestedId);
        } catch (NotFoundException ex) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
        }
    }

    @ApiOperation(value = "Get available nested resource projections and their representations",
            nickname = "Get projections", produces = MediaType.APPLICATION_JSON_VALUE)
    @GetMapping("/projections")
    public List getRepresentations() {
        return projectionService.getEntityRepresentations(extractNestedEntityType(),
                this::getSingleDefaultProjection, this::getCollectionDefaultProjection);
    }

    public BodyBuilder buildHttpGETResponseEntity(HttpStatus status) {
        return ResponseEntity.status(status);
    }

    protected String getSingleDefaultProjection() {
        return ProjectionService.NOP_PROJECTION;
    }

    public List getSingleAllowedProjections() {
        return Collections.emptyList();
    }

    protected String getCollectionDefaultProjection() {
        return ProjectionService.NOP_PROJECTION;
    }

    public List getCollectionAllowedProjections() {
        return Collections.emptyList();
    }

    private final > Class extractNestedEntityType() {
        ParameterizedType classType = (ParameterizedType) getClass().getGenericSuperclass();
        return (Class) classType.getActualTypeArguments()[4];
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy