Skip to content
This repository has been archived by the owner on May 26, 2020. It is now read-only.

Commit

Permalink
split StorableFile into StorableFile and RetrievableFile
Browse files Browse the repository at this point in the history
  • Loading branch information
amckenzie authored and Matt Rich committed Jan 23, 2017
1 parent 310fc58 commit 10f960c
Show file tree
Hide file tree
Showing 62 changed files with 2,897 additions and 1,346 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ Thumbs.db
/components/event/event-listener/conf/login.config
/components/event/event-listener/conf/openejb.xml
/components/event/event-listener/conf/users.properties
/file-service-it/conf/groups.properties
/file-service-it/conf/openejb.xml
/file-service-it/conf/users.properties
/file-service-it/conf/login.config
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
# file-service
# File Service [![Build Status](https://travis-ci.org/CJSCommonPlatform/file-service.svg?branch=master)](https://github.com/CJSCommonPlatform/file-service)

A simple drop in library for storing binary files in Postgres. This library assumes that it is running inside a JEE container with a datasource configured to point at Postgres

### Maven

<dependency>
<groupId>uk.gov.justice.services</groupId>
<artifactId>file-service-persistence</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>


### To Use

final JsonObject metadata = ...
final InputStream fileContentStream = ...

final UUID fileId = fileStorer.store(metadata, fileContentStream);

final Optional<FileReference> fileReference = fileRetriever.retrieve(fileId);
if(fileReference.isPresent()) {

// always remember to close the input streams
// as this will also close the database Connection
try(final InputStream inputStream = fileReference.get().getContentStream()) {
// do stuff...
}
}

### Database and JNDI setup

There needs to be a Postgres DataSource configured in your container with the JNDI name

java:/app/fileservice/DS.fileservice

There is a liquibase script configured to run against a local database. This database should be created as follows:

database name: fileservice
username: fileservice
password: fileservice
port: 5432

To run liquibase there is a bash script in the root folder which will create the tables for you. To run:

./runLiquibase.sh

### Issues

* This implementation is currently Postgres specific: to take advantage of Postgres' *JsonB* data type.
* The integration tests use H2 as an in memory database.
* This implementation currently stores the binary file data in Postgres using it's *bytea* data type. Although bytea will allow storage of up to 1 Gb of data, it isn't recommended to store anything larger than 100 Mb as larger files will unreasonably slow down the database.
* There is an issue with returning the InputStream of the binary content stored in the database. Although closing the SQL Connection before the InputStream is read and closed doesn't cause any issues by itself, using a pooled Connection can cause problems. If the Connection is given to a new request then the InputStream would be null and the previous request would fail. For this reason, we wrap the InputStream in our own InputStream that contains a reference to the database Connection used to read the file. When **close()** is called on the InputStream then the corresponding SQL Connection is also closed. For this reason it is vitally important to remember to close the InputStream in FileReference after use.
40 changes: 40 additions & 0 deletions file-service-api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>file-service</artifactId>
<groupId>uk.gov.justice.services</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>file-service-api</artifactId>

<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>


</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package uk.gov.justice.services.fileservice.api;

public class ConfigurationException extends FileServiceException {

public ConfigurationException(final String message) {
super(message);
}

public ConfigurationException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package uk.gov.justice.services.fileservice.api;

public class DataIntegrityException extends FileServiceException {

public DataIntegrityException(final String message) {
super(message);
}

public DataIntegrityException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package uk.gov.justice.services.fileservice.api;


import uk.gov.justice.services.fileservice.domain.FileReference;

import java.util.Optional;
import java.util.UUID;

import javax.json.JsonObject;

/**
* Entry point for retrieving files from the File Server. Inject This class into you code to use.
*/
public interface FileRetriever {

/**
* Retrieves the file corresponding to the specified file id,
*
* @param fileId The id of the file to be retrieved.
* @return an {@link Optional} containing a {@link FileReference} to the requested file, or
* {@code empty()} if none exists
*/
Optional<FileReference> retrieve(final UUID fileId) throws FileServiceException;


/**
* Retrieves the metadata for the file corresponding to the specified file id.
*
* @param fileId The id of the file who's metadata is to be retrieved
* @return an {@link Optional} containing the metadata of the requested file as a {@link JsonObject}
* or {@code empty()} if none exists
*/
Optional<JsonObject> retrieveMetadata(final UUID fileId) throws FileServiceException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package uk.gov.justice.services.fileservice.api;

public class FileServiceException extends Exception {

public FileServiceException(final String message) {
super(message);
}

public FileServiceException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package uk.gov.justice.services.fileservice.api;


import java.io.InputStream;
import java.util.UUID;

import javax.json.JsonObject;

/**
* Entry point for Storing files on the File Server. Inject This class into you code to use.
*/
public interface FileStorer {

/**
* Stores a new file on the File Server
*
* @param metadata A {@link JsonObject} of the file's metadata
* @param fileContentStream an {@link InputStream} from the file to store
* @return The id of the newly stored file.
*/
UUID store(final JsonObject metadata, final InputStream fileContentStream) throws FileServiceException;

/**
* Updates the metadata of the file stored with the specifed id
*
* @param fileId The id of the file who's metadata is to be updated
* @param metadata The metadata to update.
*/
void updateMetadata(final UUID fileId, final JsonObject metadata) throws FileServiceException;

/**
* Deletes a file from the file server
*
* @param fileId The id of the file to be deleted.
*/
void delete(final UUID fileId) throws FileServiceException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package uk.gov.justice.services.fileservice.api;

public class StorageException extends FileServiceException {

public StorageException(final String message) {
super(message);
}

public StorageException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package uk.gov.justice.services.fileservice.domain;

import static java.util.Objects.hash;

import java.io.InputStream;
import java.util.Objects;
import java.util.UUID;

import javax.json.JsonObject;

/**
* Reference to a file stored on the file server. Contains the file's id, the file's metadata as
* a {@link JsonObject} and an {@link InputStream} of the file's content.
*/
public class FileReference {

private final UUID fileId;
private final JsonObject metadata;
private final InputStream contentStream;

public FileReference(final UUID fileId, final JsonObject metadata, final InputStream contentStream) {
this.fileId = fileId;
this.metadata = metadata;
this.contentStream = contentStream;
}

/**
* @return The id of the file
*/
public UUID getFileId() {
return fileId;
}

/**
* @return The metadata of the file as a {@link JsonObject}
*/
public JsonObject getMetadata() {
return metadata;
}

/**
* @return An {@link InputStream} of the stored file.
*/
public InputStream getContentStream() {
return contentStream;
}

@Override
public String toString() {
return "FileReference{" +
"fileId=" + fileId +
", metadata=" + metadata +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package uk.gov.justice.services.fileservice.io;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;

public class InputStreamWrapper extends InputStream {

private final InputStream inputStream;
private final Connection connection;

public InputStreamWrapper(
final InputStream inputStream,
final Connection connection) {
this.inputStream = inputStream;
this.connection = connection;
}


@Override
public void close() throws IOException {

try {
close(inputStream);
} finally {
close(connection);
}
}


@Override
public int read() throws IOException {
return inputStream.read();
}

@Override
public int read(byte b[]) throws IOException {
return inputStream.read(b);
}

@Override
public int read(byte b[], int off, int len) throws IOException {
return inputStream.read(b, off, len);
}

@Override
public long skip(long n) throws IOException {
return inputStream.skip(n);
}

@Override
public int available() throws IOException {
return inputStream.available();
}

@Override
public void mark(int readLimit) {
inputStream.mark(readLimit);
}

@Override
public void reset() throws IOException {
inputStream.reset();
}

@Override
public boolean markSupported() {
return inputStream.markSupported();
}

private void close(final AutoCloseable autoCloseable) throws IOException {
try {
autoCloseable.close();
} catch (Exception e) {
throw new IOException("Failed to close " + autoCloseable.getClass().getSimpleName(), e);
}
}

public InputStream getInputStream() {
return inputStream;
}

public Connection getConnection() {
return connection;
}
}
Loading

0 comments on commit 10f960c

Please sign in to comment.