-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merged in SHIBUI-754 (pull request #170)
SHIBUI-754: Re-order filters REST API Approved-by: Shibui Jenkins <shibui.jenkins@gmail.com>
- Loading branch information
Showing
3 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
...internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
...ava/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
...bboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |