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

Commit

Permalink
add automatic media type detection of the file
Browse files Browse the repository at this point in the history
  • Loading branch information
amckenzie committed Mar 13, 2017
1 parent b1fbbb0 commit 4b38331
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 25 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ on [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to
[Semantic Versioning](http://semver.org/).

## Unreleased
### Added
- add automatic detection of the file content type

## [1.3.0] - 2017-02-28
## [1.4.0] - 2017-02-28
### Fixed
- Re-released due to a failed artifactory deployment

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A simple drop in library for storing binary files in Postgres. This library assu
<dependency>
<groupId>uk.gov.justice.services</groupId>
<artifactId>file-service-persistence</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.5.0</version>
</dependency>


Expand Down
6 changes: 6 additions & 0 deletions file-service-api/src/main/resources/META-INF/beans.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
version="1.1" bean-discovery-mode="all">
</beans>
13 changes: 13 additions & 0 deletions file-service-it/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>uk.gov.justice.utils</groupId>
<artifactId>utilities-core</artifactId>
<version>${cpp.utilities.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>uk.gov.justice.utils</groupId>
<artifactId>utilities-file</artifactId>
<version>${cpp.utilities.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>uk.gov.justice.utils</groupId>
<artifactId>test-utils-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import uk.gov.justice.services.jdbc.persistence.InitialContextFactory;
import uk.gov.justice.services.test.utils.core.files.ClasspathFileResource;
import uk.gov.justice.services.test.utils.core.jdbc.LiquibaseDatabaseBootstrapper;
import uk.gov.justice.services.utilities.file.ContentTypeDetector;

import java.io.File;
import java.io.FileInputStream;
Expand Down Expand Up @@ -79,7 +80,10 @@ public class FileServiceIT {
MetadataJdbcRepository.class,
MetadataUpdater.class,
UtcClock.class,
LogFactory.class
LogFactory.class,

MetadataUpdater.class,
ContentTypeDetector.class
})
public WebApp war() {
return new WebApp()
Expand Down Expand Up @@ -119,6 +123,7 @@ public void shouldSuccessfullyStoreABinaryFile() throws Exception {

final JsonObject retrievedMetadata = fileReference.getMetadata();
assertThat(retrievedMetadata.getString("metadataField"), is("metadataValue"));
assertThat(retrievedMetadata.getString("mediaType"), is("image/jpeg"));
assertThat(retrievedMetadata.getString("createdAt"), is(notNullValue() ));

final InputStream contentStream = fileReference.getContentStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import uk.gov.justice.services.fileservice.repository.MetadataUpdater;
import uk.gov.justice.services.jdbc.persistence.InitialContextFactory;
import uk.gov.justice.services.test.utils.core.jdbc.LiquibaseDatabaseBootstrapper;
import uk.gov.justice.services.utilities.file.ContentTypeDetector;

import java.io.ByteArrayInputStream;
import java.sql.Connection;
Expand Down Expand Up @@ -75,7 +76,8 @@ public class FileServiceTransactionsIT {
FileStore.class,
FailingMetadataJdbcRepository.class,
MetadataUpdater.class,
UtcClock.class
UtcClock.class,
ContentTypeDetector.class
})
public WebApp war() {
return new WebApp()
Expand Down
7 changes: 7 additions & 0 deletions file-service-persistence/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@
<dependency>
<groupId>uk.gov.justice.utils</groupId>
<artifactId>utilities-core</artifactId>
<version>${cpp.utilities.version}</version>
</dependency>
<dependency>
<groupId>uk.gov.justice.utils</groupId>
<artifactId>utilities-file</artifactId>
<version>${cpp.utilities.version}</version>
</dependency>


<!-- Test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import uk.gov.justice.services.fileservice.domain.FileReference;
import uk.gov.justice.services.fileservice.repository.FileStore;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -34,7 +35,9 @@ public class FileService implements FileStorer, FileRetriever {
*/
@Override
public UUID store(final JsonObject metadata, final InputStream fileContentStream) throws FileServiceException {
return fileStore.store(metadata, fileContentStream);


return fileStore.store(metadata, new BufferedInputStream(fileContentStream));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package uk.gov.justice.services.fileservice.repository;

/**
* Gets the sql for insert/updates when not using postgres JsonB
*/
public class AnsiMetadataSqlProvider implements MetadataSqlProvider {

static final String INSERT_SQL = "INSERT INTO metadata(metadata, file_id) values (?, ?)";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import uk.gov.justice.services.fileservice.domain.FileReference;
import uk.gov.justice.services.fileservice.io.InputStreamWrapper;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
Expand Down Expand Up @@ -49,12 +50,12 @@ public class FileStore {
@Transactional
public UUID store(
final JsonObject metadata,
final InputStream contentStream) throws FileServiceException {
final BufferedInputStream contentStream) throws FileServiceException {

try (final Connection connection = dataSourceProvider.getDatasource().getConnection()) {
final UUID fileId = randomUUID();

final JsonObject updatedMetadata = metadataUpdater.addCreatedTime(metadata);
final JsonObject updatedMetadata = metadataUpdater.addMediaTypeAndCreatedTime(metadata, contentStream);

contentJdbcRepository.insert(fileId, contentStream, connection);
metadataJdbcRepository.insert(fileId, updatedMetadata, connection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,64 @@

import uk.gov.justice.services.common.converter.ZonedDateTimes;
import uk.gov.justice.services.common.util.UtcClock;
import uk.gov.justice.services.fileservice.api.StorageException;
import uk.gov.justice.services.utilities.file.ContentTypeDetector;

import java.io.BufferedInputStream;
import java.io.IOException;

import javax.inject.Inject;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.MediaType;

/**
* Adds default required fields to the metadata json. Currently these fields are:
*
* mediaType: deduced from the input stream if not already present in the json
* currentTime: current utc time when the file stored
*/
public class MetadataUpdater {

@Inject
ContentTypeDetector contentTypeDetector;

@Inject
UtcClock utcClock;

public JsonObject addCreatedTime(final JsonObject metadata) {
return createObjectBuilder(metadata)
.add("createdAt", ZonedDateTimes.toString(utcClock.now()))
.build();
/**
* Adds mediaType and currentTime to the metadata json.
*
* If mediaType is already present in the json then not changed.
* If no mediaType found in the json then the mediaType is deduced from the InputStream.
* If no mediaType can be deduced AND none found prevously in the json then the default of
* "application/octet-stream" is used
*
*
* @param metadata the metadata json to be updated
* @param contentStream the file content stream, used to deduce the mediaType
* @return the updated json
* @throws StorageException if reading the {@link BufferedInputStream} throws an {@link IOException}
*/
public JsonObject addMediaTypeAndCreatedTime(final JsonObject metadata, final BufferedInputStream contentStream) throws StorageException {

final JsonObjectBuilder objectBuilder = createObjectBuilder(metadata)
.add("createdAt", ZonedDateTimes.toString(utcClock.now()));

if (! metadata.containsKey("mediaType")) {
objectBuilder.add("mediaType", getMediaTypeFrom(contentStream));
}

return objectBuilder.build();
}

private String getMediaTypeFrom(final BufferedInputStream contentStream) throws StorageException {
try {
final MediaType mediaType = contentTypeDetector.detectContentTypeOf(contentStream);
return mediaType.getType() + "/" + mediaType.getSubtype();
} catch (final IOException e) {
throw new StorageException("Failed to get media type of file from InputStream", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@
import static java.util.Optional.of;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Optional;
import java.util.UUID;

import javax.json.JsonObject;

import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
Expand All @@ -32,15 +37,23 @@ public class FileServiceTest {
@InjectMocks
private FileService fileService;

@Captor
private ArgumentCaptor<BufferedInputStream> streamCaptor;

@Test
public void shouldStoreAFile() throws Exception {

final JsonObject metadata = mock(JsonObject.class);
final InputStream content = new ByteArrayInputStream("the file content".getBytes());
final String content = "the file content";
final InputStream contentStream = new ByteArrayInputStream(content.getBytes());

fileService.store(metadata, contentStream);

verify(fileStore).store(eq(metadata), streamCaptor.capture());

fileService.store(metadata, content);
final BufferedInputStream bufferedInputStream = streamCaptor.getValue();

verify(fileStore).store(metadata, content);
assertThat(IOUtils.toString(bufferedInputStream), is(content));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import uk.gov.justice.services.fileservice.domain.FileReference;
import uk.gov.justice.services.fileservice.io.InputStreamWrapper;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
Expand Down Expand Up @@ -71,11 +72,11 @@ public void shouldInsertBothTheContentAndTheMetadata() throws Exception {
final DataSource dataSource = mock(DataSource.class);
final JsonObject metadata = mock(JsonObject.class);
final JsonObject updatedMetadata = mock(JsonObject.class);
final InputStream contentStream = mock(InputStream.class);
final BufferedInputStream contentStream = mock(BufferedInputStream.class);

when(dataSourceProvider.getDatasource()).thenReturn(dataSource);
when(dataSource.getConnection()).thenReturn(connection);
when(metadataUpdater.addCreatedTime(metadata)).thenReturn(updatedMetadata);
when(metadataUpdater.addMediaTypeAndCreatedTime(metadata, contentStream)).thenReturn(updatedMetadata);

final UUID fileId = fileStore.store(metadata, contentStream);

Expand Down Expand Up @@ -103,11 +104,11 @@ public void shouldCloseTheConnectionWhenAnExceptionIsThrownOnInsert() throws Exc
final DataSource dataSource = mock(DataSource.class);
final JsonObject metadata = mock(JsonObject.class);
final JsonObject updatedMetadata = mock(JsonObject.class);
final InputStream contentStream = mock(InputStream.class);
final BufferedInputStream contentStream = mock(BufferedInputStream.class);

when(dataSourceProvider.getDatasource()).thenReturn(dataSource);
when(dataSource.getConnection()).thenReturn(connection);
when(metadataUpdater.addCreatedTime(metadata)).thenReturn(updatedMetadata);
when(metadataUpdater.addMediaTypeAndCreatedTime(metadata, contentStream)).thenReturn(updatedMetadata);

doThrow(fileServiceException).when(metadataJdbcRepository).insert(any(UUID.class), eq(updatedMetadata), eq(connection));

Expand All @@ -126,7 +127,7 @@ public void shouldThrowStorageExceptionAIfStoringAFileFails() throws Exception {

final DataSource dataSource = mock(DataSource.class);
final JsonObject metadata = mock(JsonObject.class);
final InputStream contentStream = mock(InputStream.class);
final BufferedInputStream contentStream = mock(BufferedInputStream.class);

when(dataSourceProvider.getDatasource()).thenReturn(dataSource);
when(dataSource.getConnection()).thenThrow(sqlException);
Expand Down
Loading

0 comments on commit 4b38331

Please sign in to comment.