Skip to content

Commit

Permalink
Merge pull request #45 from TAMULib/B-03696-redis-resource-store
Browse files Browse the repository at this point in the history
Redis resource store
  • Loading branch information
jcreel committed Dec 11, 2018
2 parents 8a2e055 + 009ffb9 commit 3807736
Show file tree
Hide file tree
Showing 32 changed files with 766 additions and 51 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
| **URL** | ```/fedora/presentation/**/*``` |
| **Method** | **GET** |
| **URL Parameters** | **Optional:**<br/>```update=[boolean]```<br/>```allow=[semicolon separated string of MIME types]```<br/>```disallow=[semicolon separated string of MIME types]``` |
| **Success Response** | **Code:** 200 OK<br/>**Content:**<br/>```{ ```<br/>&emsp;```"@context" : "http://iiif.io/api/presentation/2/context.json", ```<br/>&emsp;```"@id" : "http://localhost:9003/fedora/presentation/cars_pcdm_objects/vintage", ```<br/>&emsp;```"@type" : "sc:Manifest", ```<br/>&emsp;```"description" : "Vintage", ```<br/>&emsp;```"label" : "Vintage", ```<br/>&emsp;```"logo" : "https://localhost/assets/downloads/logos/Logo.png", ```<br/>&emsp;```"metadata" : [ { ```<br/>&emsp;&emsp;```"label" : "Title", ```<br/>&emsp;&emsp;```"value" : "Vintage" ```<br/>&emsp;```}, { ```<br/>&emsp;&emsp;```"label" : "Description", ```<br/>&emsp;&emsp;```"value" : "A vintage car" ```<br/>&emsp;```} ], ```<br/>&emsp;```"sequences" : [ { ```<br/>&emsp;&emsp;```"@id" : "http://localhost:9003/fedora/sequence/cars_pcdm_objects/vintage", ```<br/>&emsp;&emsp;```"@type" : "sc:Sequence", ```<br/>&emsp;&emsp;```"canvases" : [ { ```<br/>&emsp;&emsp;&emsp;```"@id" : "http://localhost:9003/fedora/canvas/cars_pcdm_objects/vintage/pages/page_0", ```<br/>&emsp;&emsp;&emsp;```"@type" : "sc:Canvas", ```<br/>&emsp;&emsp;&emsp;```"height" : 1080, ```<br/>&emsp;&emsp;&emsp;```"images" : [ { ```<br/>&emsp;&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn/info.json", ```<br/>&emsp;&emsp;&emsp;&emsp;```"@type" : "oa:Annotation", ```<br/>&emsp;&emsp;&emsp;&emsp;```"motivation" : "sc:painting", ```<br/>&emsp;&emsp;&emsp;&emsp;```"on" : "http://localhost:9003/fedora/canvas/cars_pcdm_objects/vintage/pages/page_0", ```<br/>&emsp;&emsp;&emsp;&emsp;```"resource" : { ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn/full/full/0/default.jpg", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"@type" : "dctypes:Image", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"format" : "image/jpeg", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"height" : 1080, ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"service" : { ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"label" : "Fedora IIIF Image Resource Service", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"profile" : "http://iiif.io/api/image/2/level0.json", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"@context" : "http://iiif.io/api/image/2/context.json", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn" ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```}, ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"width" : 1920 ```<br/>&emsp;&emsp;&emsp;&emsp;```} ```<br/>&emsp;&emsp;&emsp;```} ], ```<br/>&emsp;&emsp;&emsp;```"label" : "Page 0", ```<br/>&emsp;&emsp;&emsp;```"metadata" : [ ], ```<br/>&emsp;&emsp;&emsp;```"width" : 1920 ```<br/>&emsp;&emsp;```} ], ```<br/>&emsp;&emsp;```"label" : "Vintage" ```<br/>&emsp;```} ], ```<br/>&emsp;```"thumbnail" : { ```<br/>&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn/full/!200,200/0/default.jpg", ```<br/>&emsp;&emsp;```"services" : [ { ```<br/>&emsp;&emsp;&emsp;```"@context" : "http://iiif.io/api/image/2/context.json", ```<br/>&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn", ```<br/>&emsp;&emsp;&emsp;```"label" : "Fedora IIIF Image Resource Service", ```<br/>&emsp;&emsp;&emsp;```"profile" : "http://iiif.io/api/image/2/level0.json" ```<br/>&emsp;&emsp;```} ] ```<br/>&emsp;```} ```<br/>```}``` |
| **Success Response** | **Code:** 200 OK<br/>**Content:**<br/>```{ ```<br/>&emsp;```"@context" : "http://iiif.io/api/presentation/2/context.json", ```<br/>&emsp;```"@id" : "http://localhost:9003/fedora/presentation/cars_pcdm_objects/vintage", ```<br/>&emsp;```"@type" : "sc:Manifest", ```<br/>&emsp;```"description" : "Vintage", ```<br/>&emsp;```"label" : "Vintage", ```<br/>&emsp;```"logo" : "https://localhost/assets/downloads/logos/Logo.png", ```<br/>&emsp;```"metadata" : [ { ```<br/>&emsp;&emsp;```"label" : "Title", ```<br/>&emsp;&emsp;```"value" : "Vintage" ```<br/>&emsp;```}, { ```<br/>&emsp;&emsp;```"label" : "Description", ```<br/>&emsp;&emsp;```"value" : "A vintage car" ```<br/>&emsp;```} ], ```<br/>&emsp;```"sequences" : [ { ```<br/>&emsp;&emsp;```"@id" : "http://localhost:9003/fedora/sequence/cars_pcdm_objects/vintage", ```<br/>&emsp;&emsp;```"@type" : "sc:Sequence", ```<br/>&emsp;&emsp;```"canvases" : [ { ```<br/>&emsp;&emsp;&emsp;```"@id" : "http://localhost:9003/fedora/canvas/cars_pcdm_objects/vintage/pages/page_0", ```<br/>&emsp;&emsp;&emsp;```"@type" : "sc:Canvas", ```<br/>&emsp;&emsp;&emsp;```"height" : 1080, ```<br/>&emsp;&emsp;&emsp;```"images" : [ { ```<br/>&emsp;&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn/info.json", ```<br/>&emsp;&emsp;&emsp;&emsp;```"@type" : "oa:Annotation", ```<br/>&emsp;&emsp;&emsp;&emsp;```"motivation" : "sc:painting", ```<br/>&emsp;&emsp;&emsp;&emsp;```"on" : "http://localhost:9003/fedora/canvas/cars_pcdm_objects/vintage/pages/page_0", ```<br/>&emsp;&emsp;&emsp;&emsp;```"resource" : { ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn/full/full/0/default.jpg", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"@type" : "dctypes:Image", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"format" : "image/jpeg", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"height" : 1080, ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"service" : { ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"label" : "Fedora IIIF Image Resource Service", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"profile" : "http://iiif.io/api/image/2/level0.json", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"@context" : "http://iiif.io/api/image/2/context.json", ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn" ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```}, ```<br/>&emsp;&emsp;&emsp;&emsp;&emsp;```"width" : 1920 ```<br/>&emsp;&emsp;&emsp;&emsp;```} ```<br/>&emsp;&emsp;&emsp;```} ], ```<br/>&emsp;&emsp;&emsp;```"label" : "Page 0", ```<br/>&emsp;&emsp;&emsp;```"metadata" : [ ], ```<br/>&emsp;&emsp;&emsp;```"width" : 1920 ```<br/>&emsp;&emsp;```} ], ```<br/>&emsp;&emsp;```"label" : "Vintage" ```<br/>&emsp;```} ], ```<br/>&emsp;```"thumbnail" : { ```<br/>&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn/full/!100,100/0/default.jpg", ```<br/>&emsp;&emsp;```"services" : [ { ```<br/>&emsp;&emsp;&emsp;```"@context" : "http://iiif.io/api/image/2/context.json", ```<br/>&emsp;&emsp;&emsp;```"@id" : "http://localhost:8182/iiif/2/ZmVkb3JhOmNhcnNfcGNkbV9vYmplY3RzL3ZpbnRhZ2UvcGFnZXMvcGFnZV8wL2ZpbGVzL3ZpbnRhZ2UuanBn", ```<br/>&emsp;&emsp;&emsp;```"label" : "Fedora IIIF Image Resource Service", ```<br/>&emsp;&emsp;&emsp;```"profile" : "http://iiif.io/api/image/2/level0.json" ```<br/>&emsp;&emsp;```} ] ```<br/>&emsp;```} ```<br/>```}``` |
| **Error Response** | **Code:** 404 NOT_FOUND<br/>**Content:** ```Fedora PCDM RDF not found!``` |
| **Error Response** | **Code:** 503 SERVICE_UNAVAILABLE<br/>**Content:** ```[Exception message]``` |
| **Sample Request** | ```/fedora/presentation/cars_pcdm_objects/vintage``` |
Expand Down
34 changes: 32 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -26,7 +27,6 @@
<java.version>1.8</java.version>
<jena-libs.version>3.2.0</jena-libs.version>
<iiif-presentation.version>3.2.5</iiif-presentation.version>
<iiif-image.version>2.2.6</iiif-image.version>
</properties>

<dependencies>
Expand All @@ -36,6 +36,11 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
Expand All @@ -44,6 +49,12 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
Expand All @@ -58,12 +69,31 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version><!--$NO-MVN-MAN-VER$ -->
</dependency>

<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.6</version>
</dependency>

<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>apache-jena-libs</artifactId>
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/edu/tamu/iiif/config/AdminConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package edu.tamu.iiif.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "iiif")
public class AdminConfig {

private List<Credentials> admins = new ArrayList<Credentials>();

public List<Credentials> getAdmins() {
return admins;
}

public void setAdmins(List<Credentials> admins) {
this.admins = admins;
}

public static class Credentials {

private String username;

private String password;

public Credentials() {

}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

}

}
61 changes: 61 additions & 0 deletions src/main/java/edu/tamu/iiif/config/WebSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package edu.tamu.iiif.config;

import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

import edu.tamu.iiif.config.AdminConfig.Credentials;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AdminConfig adminConfig;

@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers(POST, "/resources")
.hasRole("ADMIN")
.antMatchers(PUT, "/resources")
.hasRole("ADMIN")
.antMatchers(DELETE, "/resources/*")
.hasRole("ADMIN")
.anyRequest()
.permitAll()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// @formatter:on
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
for (Credentials adminCredentials : adminConfig.getAdmins()) {
// @formatter:off
auth
.inMemoryAuthentication()
.withUser(adminCredentials.getUsername())
.password(adminCredentials.getPassword())
.roles("ADMIN");
// @formatter:on
}

}

}
79 changes: 79 additions & 0 deletions src/main/java/edu/tamu/iiif/controller/ResourceController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package edu.tamu.iiif.controller;

import static org.springframework.http.HttpStatus.MOVED_PERMANENTLY;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;

import java.io.IOException;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

import edu.tamu.iiif.exception.InvalidUrlException;
import edu.tamu.iiif.exception.NotFoundException;
import edu.tamu.iiif.model.RedisResource;
import edu.tamu.iiif.model.repo.RedisResourceRepo;

@RestController
@RequestMapping("/resources")
public class ResourceController {

@Autowired
private RedisResourceRepo redisResourceRepo;

@GetMapping(value = "/{id}", produces = "text/plain")
public String getResourceUrl(@PathVariable String id) throws NotFoundException {
Optional<RedisResource> redisFileResolver = Optional.ofNullable(redisResourceRepo.findOne(id));
if (redisFileResolver.isPresent()) {
return redisFileResolver.get().getUrl();
}
throw new NotFoundException(String.format("Unable to resolve resource with id %s", id));
}

@GetMapping(value = "/{id}/redirect")
public RedirectView redirectToResource(@PathVariable String id) throws IOException, NotFoundException {
Optional<RedisResource> redisFileResolver = Optional.ofNullable(redisResourceRepo.findOne(id));
if (redisFileResolver.isPresent()) {
RedirectView redirect = new RedirectView(redisFileResolver.get().getUrl());
redirect.setStatusCode(MOVED_PERMANENTLY);
return redirect;
}
throw new NotFoundException(String.format("Unable to resolve resource with id %s", id));

}

@GetMapping(value = "/lookup", produces = "text/plain")
public String getResourceId(@RequestParam(value = "uri", required = true) String uri) throws NotFoundException {
Optional<RedisResource> redisFileResolver = redisResourceRepo.findByUrl(uri);
if (redisFileResolver.isPresent()) {
return redisFileResolver.get().getId();
}
throw new NotFoundException(String.format("No resourse found with uri %s", uri));
}

@ResponseStatus(HttpStatus.CREATED)
@RequestMapping(method = { POST, PUT }, produces = "text/plain")
public String addResource(@RequestParam(value = "uri", required = true) String uri) throws InvalidUrlException {
return redisResourceRepo.getOrCreate(uri).getId();
}

@DeleteMapping(value = "/{id}", produces = "text/plain")
public String removeResource(@PathVariable String id) throws NotFoundException {
Optional<RedisResource> redisFileResolver = Optional.ofNullable(redisResourceRepo.findOne(id));
if (redisFileResolver.isPresent()) {
redisResourceRepo.delete(redisFileResolver.get());
return "Success";
}
throw new NotFoundException(String.format("Unable to resolve resource with id %s", id));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public class GlobalExceptionHandler {
public @ResponseBody ResponseEntity<String> handleIOException(IOException exception) {
ResponseEntity<String> response;
if (StringUtils.containsIgnoreCase(ExceptionUtils.getRootCauseMessage(exception), "Broken pipe")) {
LOG.debug("Client has disconnected before completing request.");
LOG.debug("Client has disconnected before completing request.", exception);
response = null;
} else {
LOG.debug("Exception completing request. " + exception.getMessage());
LOG.debug(exception.getMessage(), exception);
response = new ResponseEntity<String>(exception.getMessage(), SERVICE_UNAVAILABLE);
}
return response;
Expand All @@ -43,21 +43,21 @@ public class GlobalExceptionHandler {
@ExceptionHandler(RiotException.class)
@ResponseStatus(value = SERVICE_UNAVAILABLE)
public @ResponseBody ResponseEntity<String> handleRiotException(RiotException exception) {
LOG.debug("Exception requesting RDF. " + exception.getMessage());
LOG.debug(exception.getMessage(), exception);
return new ResponseEntity<String>(exception.getMessage(), SERVICE_UNAVAILABLE);
}

@ExceptionHandler(RedisConnectionFailureException.class)
@ResponseStatus(value = SERVICE_UNAVAILABLE)
public @ResponseBody ResponseEntity<String> handleRedisConnectionFailureException(RedisConnectionFailureException exception) {
LOG.debug("Exception requesting RDF. " + exception.getMessage());
LOG.debug(exception.getMessage(), exception);
return new ResponseEntity<String>(exception.getMessage(), SERVICE_UNAVAILABLE);
}

@ExceptionHandler(NotFoundException.class)
@ResponseStatus(value = NOT_FOUND)
public @ResponseBody ResponseEntity<String> handleNotFoundException(NotFoundException exception) {
LOG.debug("Exception requesting RDF. " + exception.getMessage());
LOG.debug(exception.getMessage(), exception);
return new ResponseEntity<String>(exception.getMessage(), NOT_FOUND);
}

Expand Down
13 changes: 13 additions & 0 deletions src/main/java/edu/tamu/iiif/exception/InvalidUrlException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package edu.tamu.iiif.exception;

import java.io.IOException;

public class InvalidUrlException extends IOException {

private static final long serialVersionUID = -6351161650912426116L;

public InvalidUrlException(String message) {
super(message);
}

}
Loading

0 comments on commit 3807736

Please sign in to comment.