package api; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; import java.net.URI; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import resources.AbstractAdminResourceAssembler; import resources.AdminResourceView; import resources.shared.query.AdminCollectionQueryRequest; import resources.shared.query.AdminResourceQueryRequest; import resources.PagedResource; import resources.PagedResource.NoResource; import resources.PagedResourceContext; import resources.Resource; import resources.ResourceUId; import resources.controls.ControlSupport; import resources.media.query.MediaQuery; import exception.ServerException; import util.SuppressFBWarnings; public abstract class AbstractAdminRestController, P extends PagedResource, F extends ControlSupport> { private static final Set DELETE_ALLOWED_METHODS = ImmutableSet.of( HttpMethod.GET, HttpMethod.DELETE ); private static final Set GET_ALLOWED_METHODS = ImmutableSet.of( HttpMethod.GET ); private final AbstractAdminResourceAssembler assembler; private final boolean deleteAllowed; private final String apiRootMapping; protected AbstractAdminRestController( AbstractAdminResourceAssembler assembler, boolean deleteAllowed ) { this.assembler = assembler; this.deleteAllowed = deleteAllowed; this.apiRootMapping = resolveRequestMapping( getClass() ); } public abstract ResponseEntity

collection( List q, String view, long page, long limit ) throws ServerException; public abstract ResponseEntity item( String id, String view ) throws ServerException; public abstract ResponseEntity itemPermalink( String slug ) throws ServerException; public abstract ResponseEntity

queryBuilder( @RequestBody AdminResourceQueryRequest query ) throws ServerException; public abstract ResponseEntity queryBuilderForm( List q, String view, long page, long limit ) throws ServerException; public final ResponseEntity asItemPermalinkEntity( UID uid, LinkRelation itemRel ) { return asItemPermalinkEntity( assembler, uid, itemRel, deleteAllowed ); } public final ResponseEntity asItemEntity( UID uid, String view ) throws ServerException { AdminResourceView resourceView = AdminResourceView.forKey( view, AdminCollectionQueryRequest.DEFAULT_ITEM_VIEW_VALUE ); return asItemEntity( assembler, uid, resourceView, deleteAllowed ); } public final ResponseEntity

asCollectionEntity( PagedResourceContext context ) { return asCollectionEntity( context, deleteAllowed ); } public final String apiRootMapping() { return apiRootMapping; } public Optional queryBuilderFormLink() { return assembler.queryBuilderFormLink(); } @SuppressWarnings( "null" ) @SuppressFBWarnings( value = "NP_NONNULL_PARAM_VIOLATION", justification = "Not a real method call" ) public Optional templatedLinkToItemPermalink() { try { return Optional.of( linkTo( methodOn( this.getClass() ).itemPermalink( null ) ).withRel( assembler.related().getItemResourceRelFor( assembler.resourceClass() ) ) ); } catch ( ServerException e ) { throw new RuntimeException( e ); } } public static final > ResponseEntity asQueryBuilderFormEntity( F queryForm ) { HttpHeaders responseHeaders = new HttpHeaders(); return new ResponseEntity<>( queryForm, responseHeaders, HttpStatus.OK ); } public static final ResponseEntity asItemPermalinkEntity( AbstractAdminResourceAssembler assembler, UID uid, LinkRelation itemRel, boolean deleteAllowed ) { Link link = assembler.linkForUId( uid ).withRel( itemRel ); HttpHeaders responseHeaders = createBaseResponseHeaders( deleteAllowed ); responseHeaders.setLocation( URI.create( link.getHref() ) ); NoResource resource = new NoResource(); resource.add( link ); return new ResponseEntity<>( resource, responseHeaders, HttpStatus.SEE_OTHER ); } public static final > ResponseEntity asItemEntity( AbstractAdminResourceAssembler assembler, UID uid, AdminResourceView view, boolean deleteAllowed ) throws ServerException { R resource = assembler.fetchResource( uid, view ) .orElseThrow( assembler.notFoundExceptionFor( uid ) ); return new ResponseEntity<>( resource, createResourceResponseHeaders( resource, deleteAllowed ), HttpStatus.OK ); } public static final Optional parseMediaQuery( List q ) { MediaQuery query = MediaQuery.of( q != null ? q : ImmutableList.of() ); query.removeAllTermsOfUnknownType(); return Optional.of( query ); } public static final

> ResponseEntity

asCollectionEntity( PagedResourceContext context, boolean deleteAllowed ) { P resource = context.toPagedResource(); return new ResponseEntity<>( resource, createResourceResponseHeaders( resource, deleteAllowed ), HttpStatus.OK ); } public static HttpHeaders createBaseResponseHeaders( boolean deleteAllowed ) { HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.setAllow( deleteAllowed ? DELETE_ALLOWED_METHODS : GET_ALLOWED_METHODS ); return responseHeaders; } public static HttpHeaders createResourceResponseHeaders( @Nullable Resource resource, boolean allowDelete ) { HttpHeaders responseHeaders = createBaseResponseHeaders( allowDelete ); Optional self = Optional.ofNullable( resource ).flatMap( r -> r.getLink( IanaLinkRelations.SELF ) ); if ( self.isPresent() ) { String location = self.get().getHref(); responseHeaders.setLocation( URI.create( location ) ); responseHeaders.set( HttpHeaders.CONTENT_LOCATION, location ); } return responseHeaders; } private static String resolveRequestMapping( Class controllerType ) { RequestMapping mapping = AnnotatedElementUtils.findMergedAnnotation( controllerType, RequestMapping.class ); if ( mapping == null || ObjectUtils.isEmpty( mapping.path() ) || mapping.path().length > 1 || !StringUtils.hasLength( mapping.path()[0] ) ) { throw new IllegalStateException( "AbstractAdminRestController implementations must be annotated with @RequestMapping and contain a single base mapping" ); } return mapping.path()[0]; } }