Skip to content

Commit

Permalink
Merged in SHIBUI-754 (pull request #170)
Browse files Browse the repository at this point in the history
SHIBUI-754: Re-order filters REST API

Approved-by: Shibui Jenkins <shibui.jenkins@gmail.com>
  • Loading branch information
dima767 authored and Jonathan Johnson committed Aug 22, 2018
2 parents 62b6183 + 59ae189 commit 98aa4ba
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import edu.internet2.tier.shibboleth.admin.ui.controller.support.RestControllersSupport;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
* @author Dmitriy Kopylenko
*/
@RestController
@RequestMapping("/api/MetadataResolvers/{metadataResolverId}/FiltersPositionOrder")
public class MetadataFiltersPositionOrderController {

@Autowired
MetadataResolverRepository metadataResolverRepository;

@Autowired
RestControllersSupport restControllersSupport;

@PostMapping
@Transactional
public ResponseEntity<?> updateFiltersPositionOrder(@PathVariable String metadataResolverId,
@RequestBody List<String> filtersResourceIds) {

MetadataResolver resolver = restControllersSupport.findResolverOrThrowHttp404(metadataResolverId);
List<MetadataFilter> currentFilters = resolver.getMetadataFilters();

//Check for bad data upfront. We could avoid this check and take wrong size and/or filter ids and blindly pass to sort below.
//In that case, the sort operation will silently NOT do anything and leave original filters order,
//but we will not be able to indicate to calling clients HTTP 400 in that case.
if ((filtersResourceIds.size() != currentFilters.size()) ||
(!currentFilters.stream()
.map(MetadataFilter::getResourceId)
.collect(toList())
.containsAll(filtersResourceIds))) {

return ResponseEntity
.badRequest()
.body("Number of filters to reorder or filters resource ids do not match current filters");
}

//This is needed in order to set reference to persistent filters collection to be able to merge the persistent collection
//Otherwise if we manipulate the original collection directly and try to save, we'll get RDBMS constraint violation exception
List<MetadataFilter> reOrderedFilters = new ArrayList<>(currentFilters);

//Main re-ordering operation
reOrderedFilters.sort(Comparator.comparingInt(f -> filtersResourceIds.indexOf(f.getResourceId())));

//re-set the reference and save to DB
resolver.setMetadataFilters(reOrderedFilters);
metadataResolverRepository.save(resolver);

return ResponseEntity.noContent().build();
}

@GetMapping
@Transactional(readOnly = true)
public ResponseEntity<?> getFiltersPositionOrder(@PathVariable String metadataResolverId) {
MetadataResolver resolver = restControllersSupport.findResolverOrThrowHttp404(metadataResolverId);
List<String> resourceIds = resolver.getMetadataFilters().stream()
.map(MetadataFilter::getResourceId)
.collect(toList());

return ResponseEntity.ok(resourceIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package edu.internet2.tier.shibboleth.admin.ui.controller.support;

import com.google.common.collect.ImmutableMap;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;

import static org.springframework.http.HttpStatus.NOT_FOUND;

/**
* Common functionality for REST controllers.
*
* @author Dmitriy Kopylenko
*/
@RestControllerAdvice
public class RestControllersSupport {

@Autowired
MetadataResolverRepository resolverRepository;

public MetadataResolver findResolverOrThrowHttp404(String resolverResourceId) {
MetadataResolver resolver = resolverRepository.findByResourceId(resolverResourceId);
if(resolver == null) {
throw new HttpClientErrorException(NOT_FOUND, "Metadata resolver is not found");
}
return resolver;
}


@ExceptionHandler
public ResponseEntity<?> notFoundHandler(HttpClientErrorException ex) {
if(ex.getStatusCode() == NOT_FOUND) {
return ResponseEntity.status(NOT_FOUND).body(ex.getStatusText());
}
throw ex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository
import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator
import edu.internet2.tier.shibboleth.admin.util.AttributeUtility

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.test.context.ActiveProfiles

import spock.lang.Specification


/**
* @author Dmitriy Kopylenko
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("no-auth")
class MetadataFiltersPositionOrderControllerIntegrationTests extends Specification {

@Autowired
private TestRestTemplate restTemplate

@Autowired
MetadataResolverRepository metadataResolverRepository

@Autowired
AttributeUtility attributeUtility

TestObjectGenerator generator

static BASE_URI = '/api/MetadataResolvers'

static RESOURCE_URI = "$BASE_URI/%s/FiltersPositionOrder"

def setup() {
generator = new TestObjectGenerator(attributeUtility)
}

def cleanup() {
metadataResolverRepository.deleteAll()
}

def "GET Filter Position Order for non-existent resolver"() {
when: 'GET request is made with resolver resource id NOT matching any existing filter'
def result = getFiltersPositionOrderFor('non-existent-resolver-id', String)

then: "Request completed successfully"
result.statusCodeValue == 404
}

def "GET Default Filter Position Order for filters originally attached to a resolver"() {
given: 'MetadataResolver with 2 filters in data store'
def resolver = createResolverWithTwoFilters()

when: 'GET request is made to retrieve position order of filters'
def result = getFiltersPositionOrderFor(resolver.resourceId, List)

then: 'Original filters order is preserved'
result.statusCodeValue == 200
result.body == [resolver.firstFilterResourceId, resolver.secondFilterResourceId]
}

def "Reorder filters and verify the position within resolver persisted accordingly"() {
given: 'MetadataResolver with 2 filters in data store'
def resolver = createResolverWithTwoFilters()
def reOrderedFiltersPosition = [resolver.secondFilterResourceId, resolver.firstFilterResourceId]

when: 'POST is made to re-order filters position'
def reorderPOSTResult = reorderFilters(resolver.resourceId, reOrderedFiltersPosition)

then: 'Request completed successfully'
reorderPOSTResult.statusCodeValue == 204

and: 'GET request is made to retrieve position order of filters'
def positionOrderResult = getFiltersPositionOrderFor(resolver.resourceId, List)

then:
positionOrderResult.body == reOrderedFiltersPosition

and: "Request is made to retrieve the resolver with affected filters"
def resolverResult = getResolver(resolver.resourceId)

then:
resolverResult.statusCodeValue == 200
resolverResult.body.metadataFilters.collect {it.resourceId} == reOrderedFiltersPosition
}

def "Reorder filters with bad data"() {
given: 'MetadataResolver with 2 filters in data store'
def resolver = createResolverWithTwoFilters()
def originalFiltersPosition = [resolver.firstFilterResourceId, resolver.secondFilterResourceId]
//Only one filter in order position data, while there are two filters
def reOrderedFiltersPosition = [resolver.secondFilterResourceId]

when: 'POST is made to re-order filters position with invalid number of filters to re-order'
def reorderPOSTResult = reorderFilters(resolver.resourceId, reOrderedFiltersPosition)

then: 'Request completed successfully with 400'
reorderPOSTResult.statusCodeValue == 400

and: 'GET request is made to retrieve position order of filters'
def positionOrderResult = getFiltersPositionOrderFor(resolver.resourceId, List)

then: 'Original filters position order is retrieved'
positionOrderResult.body == originalFiltersPosition

and: "Request is made to retrieve the resolver with original filters"
def resolverResult = getResolver(resolver.resourceId)

then:
resolverResult.statusCodeValue == 200
resolverResult.body.metadataFilters.collect {it.resourceId} == originalFiltersPosition

and: 'POST is made to re-order filters position with invalid resource ids'
def reorderPOSTResult_2 = reorderFilters(resolver.resourceId, [resolver.secondFilterResourceId, 'invalid-id'])

then: 'Request completed successfully with 400'
reorderPOSTResult_2.statusCodeValue == 400
}

private createResolverWithTwoFilters() {
def resolver = generator.buildRandomMetadataResolverOfType('FileBacked')
resolver.metadataFilters = [generator.signatureValidationFilter(), generator.entityRoleWhitelistFilter()]
def resolverResourceId = resolver.resourceId
def firstFilterResourceId = resolver.metadataFilters[0].resourceId
def secondFilterResourceId = resolver.metadataFilters[1].resourceId
metadataResolverRepository.save(resolver)

[resourceId : resolverResourceId,
firstFilterResourceId : firstFilterResourceId,
secondFilterResourceId: secondFilterResourceId]
}

private getFiltersPositionOrderFor(String resourceId, responseType) {
this.restTemplate.getForEntity(resourceUriFor(resourceId), responseType)
}

private reorderFilters(String resourceId, List filterIdsPositionOrderList) {
this.restTemplate.postForEntity(resourceUriFor(resourceId), filterIdsPositionOrderList, null)
}

private getResolver(String resolverResourceId) {
this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId", Object)
}

private static resourceUriFor(String resolverResourceId) {
String.format(RESOURCE_URI, resolverResourceId)
}
}

0 comments on commit 98aa4ba

Please sign in to comment.