From d6e7b7b1c025424d39421b2b2437e1379bf35db2 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Sat, 17 Oct 2015 16:45:07 -0500 Subject: [PATCH 01/24] WIP --- cantaloupe.properties.sample | 11 + .../library/cantaloupe/cache/JdbcCache.java | 328 ++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java diff --git a/cantaloupe.properties.sample b/cantaloupe.properties.sample index f785643f3..cbff0cb23 100644 --- a/cantaloupe.properties.sample +++ b/cantaloupe.properties.sample @@ -192,6 +192,17 @@ FilesystemCache.pathname = /var/cache/cantaloupe # blank or 0 for "forever" FilesystemCache.ttl_seconds = 2592000 +### +# JdbcCache +### + +JdbcCache.connection_string = jdbc:postgresql://localhost:5432/cantaloupe +JdbcCache.user = alexd +JdbcCache.password = +JdbcCache.image_table = image_cache +JdbcCache.info_table = info_cache +JdbcCache.ttl_seconds = 10 + ############### # LOGGING ############### diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java new file mode 100644 index 000000000..48a4cb840 --- /dev/null +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -0,0 +1,328 @@ +package edu.illinois.library.cantaloupe.cache; + +import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.request.Identifier; +import edu.illinois.library.cantaloupe.request.Parameters; +import org.apache.commons.configuration.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.Dimension; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.time.Instant; + +/** + * Cache using a database table, storing images as BLOBs and image dimensions + * as integers. + */ +class JdbcCache implements Cache { + + private static final Logger logger = LoggerFactory. + getLogger(JdbcCache.class); + + private static final String HEIGHT_COLUMN = "height"; + private static final String IDENTIFIER_COLUMN = "identifier"; + private static final String IMAGE_COLUMN = "image"; + private static final String LAST_MODIFIED_COLUMN = "last_modified"; + private static final String WIDTH_COLUMN = "width"; + + private static final String CONNECTION_STRING_KEY = "JdbcCache.connection_string"; + private static final String PASSWORD_KEY = "JdbcCache.password"; + private static final String IMAGE_TABLE_KEY = "JdbcCache.image_table"; + private static final String INFO_TABLE_KEY = "JdbcCache.info_table"; + private static final String TTL_KEY = "JdbcCache.ttl_seconds"; + private static final String USER_KEY = "JdbcCache.user"; + + private static Connection connection; + + static { + try { + Connection connection = getConnection(); + logger.info("Using {} {}", connection.getMetaData().getDriverName(), + connection.getMetaData().getDriverVersion()); + Configuration config = Application.getConfiguration(); + logger.info("Connection string: {}", + config.getString("JdbcCache.connection_string")); + } catch (SQLException e) { + logger.error("Failed to establish a database connection", e); + } + } + + public static synchronized Connection getConnection() throws SQLException { + if (connection == null) { + Configuration config = Application.getConfiguration(); + final String connectionString = config. + getString(CONNECTION_STRING_KEY, ""); + final String user = config.getString(USER_KEY, ""); + final String password = config.getString(PASSWORD_KEY, ""); + connection = DriverManager.getConnection(connectionString, user, + password); + } + return connection; + } + + @Override + public void flush() throws IOException { + try { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); + int numDeletedImages, numDeletedDimensions; + + // delete the contents of the image table + final String imageTableName = config.getString(IMAGE_TABLE_KEY); + if (imageTableName != null && imageTableName.length() > 0) { + String sql = "DELETE FROM " + imageTableName; + PreparedStatement statement = conn.prepareStatement(sql); + numDeletedImages = statement.executeUpdate(); + } else { + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + + // delete the contents of the info table + final String infoTableName = config.getString(INFO_TABLE_KEY); + if (infoTableName != null && infoTableName.length() > 0) { + String sql = "DELETE FROM " + infoTableName; + PreparedStatement statement = conn.prepareStatement(sql); + numDeletedDimensions = statement.executeUpdate(); + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + + logger.info("Deleted {} cached image(s) and {} cached dimension(s)", + numDeletedImages, numDeletedDimensions); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } + + @Override + public void flush(Parameters params) throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(TABLE_NAME_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format("DELETE FROM %s WHERE identifier = ?", + tableName); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setString(1, params.getIdentifier().getValue()); + int affectedRows = statement.executeUpdate(); + logger.info("Deleted {} cached image(s)", affectedRows); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(TABLE_NAME_KEY + " is not set"); + } + } + + @Override + public void flushExpired() throws IOException { + try { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); + int numDeletedImages, numDeletedDimensions; + final Date oldestDate = getOldestValidDate(); + + // delete from the image table + final String imageTableName = config.getString(IMAGE_TABLE_KEY); + if (imageTableName != null && imageTableName.length() > 0) { + String sql = String.format("DELETE FROM %s WHERE %s < ?", + imageTableName, LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setDate(1, oldestDate); + numDeletedImages = statement.executeUpdate(); + } else { + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + + // delete from the info table + final String infoTableName = config.getString(INFO_TABLE_KEY); + if (infoTableName != null && infoTableName.length() > 0) { + String sql = String.format("DELETE FROM %s WHERE %s < ?", + infoTableName, LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setDate(1, oldestDate); + numDeletedDimensions = statement.executeUpdate(); + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + + logger.info("Deleted {} cached image(s) and {} cached dimension(s)", + numDeletedImages, numDeletedDimensions); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } + + @Override + public Dimension getDimension(Identifier identifier) throws IOException { + final Date oldestDate = getOldestValidDate(); + Configuration config = Application.getConfiguration(); + String tableName = config.getString(INFO_TABLE_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format( + "SELECT width, height FROM %s WHERE identifier = ? AND %s > ?", + tableName, LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setString(1, identifier.getValue()); + statement.setDate(2, oldestDate); + ResultSet resultSet = statement.getResultSet(); + if (resultSet.next()) { + return new Dimension(resultSet.getInt("width"), + resultSet.getInt("height")); + } + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + } + + @Override + public InputStream getImageInputStream(Parameters params) { + InputStream inputStream = null; + final Date oldestDate = getOldestValidDate(); + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format( + "SELECT %s FROM %s WHERE %s = ? AND %s > ?", + IMAGE_COLUMN, tableName, IDENTIFIER_COLUMN, + LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setString(1, params.getIdentifier().getValue()); + statement.setDate(2, oldestDate); + ResultSet resultSet = statement.getResultSet(); + if (resultSet.next()) { + inputStream = resultSet.getBinaryStream(1); + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } else { + logger.error("{} is not set", INFO_TABLE_KEY); + } + return inputStream; + } + + @Override + public OutputStream getImageOutputStream(Parameters params) + throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format("CREATE TABLE %s (" + + "id INTEGER NOT NULL PRIMARY KEY, " + + "%s VARCHAR(2048) NOT NULL, " + + "%s BLOB," + + "%s DATETIME)", + tableName, IDENTIFIER_COLUMN, IMAGE_COLUMN, + LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + + statement.execute(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + } + + private void createImageTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format("CREATE TABLE %s (" + + "id INTEGER NOT NULL PRIMARY KEY, " + + "%s VARCHAR(2048) NOT NULL, " + + "%s BLOB," + + "%s DATETIME)", + tableName, IDENTIFIER_COLUMN, IMAGE_COLUMN, + LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + } + + private void createInfoTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(INFO_TABLE_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format("CREATE TABLE %s (" + + "id INTEGER NOT NULL PRIMARY KEY, " + + "%s VARCHAR(2048) NOT NULL, " + + "%s INTEGER, " + + "%s INTEGER," + + "%s DATETIME)", + tableName, IDENTIFIER_COLUMN, WIDTH_COLUMN, + HEIGHT_COLUMN, LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + } + + private Date getOldestValidDate() { + Configuration config = Application.getConfiguration(); + final Instant oldestInstant = Instant.now(). + minus(Duration.ofSeconds(config.getLong(TTL_KEY, 0))); + return new Date(Date.from(oldestInstant).getTime()); + } + + @Override + public void putDimension(Identifier identifier, Dimension dimension) + throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(INFO_TABLE_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = String.format( + "INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)", + tableName, IDENTIFIER_COLUMN, WIDTH_COLUMN, + HEIGHT_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setString(1, identifier.getValue()); + statement.setInt(2, dimension.width); + statement.setInt(3, dimension.height); + statement.execute(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + } + +} From 1f6e8af3eec3d9e1f20fe331aae5d6ab7a27ed1c Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Mon, 19 Oct 2015 10:27:26 -0500 Subject: [PATCH 02/24] WIP --- .../library/cantaloupe/cache/Cache.java | 2 +- .../library/cantaloupe/cache/JdbcCache.java | 282 +++++++++++------- 2 files changed, 176 insertions(+), 108 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java index 626194bd5..54118541c 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java @@ -49,7 +49,7 @@ public interface Cache { * Reads cached dimension information. * * @param identifier IIIF identifier - * @return Dimension corresponding to the given identifier, or null if a + * @return Dimension corresponding to the given identifier, or null if no * non-expired dimension exists in the cache. */ Dimension getDimension(Identifier identifier) throws IOException; diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index 48a4cb840..efac9e91b 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.sql.Blob; import java.sql.Connection; import java.sql.Date; import java.sql.DriverManager; @@ -29,11 +30,13 @@ class JdbcCache implements Cache { private static final Logger logger = LoggerFactory. getLogger(JdbcCache.class); - private static final String HEIGHT_COLUMN = "height"; - private static final String IDENTIFIER_COLUMN = "identifier"; - private static final String IMAGE_COLUMN = "image"; - private static final String LAST_MODIFIED_COLUMN = "last_modified"; - private static final String WIDTH_COLUMN = "width"; + private static final String IMAGE_TABLE_IMAGE_COLUMN = "image"; + private static final String IMAGE_TABLE_LAST_MODIFIED_COLUMN = "last_modified"; + private static final String IMAGE_TABLE_PARAMS_COLUMN = "params"; + private static final String INFO_TABLE_HEIGHT_COLUMN = "height"; + private static final String INFO_TABLE_IDENTIFIER_COLUMN = "identifier"; + private static final String INFO_TABLE_LAST_MODIFIED_COLUMN = "last_modified"; + private static final String INFO_TABLE_WIDTH_COLUMN = "width"; private static final String CONNECTION_STRING_KEY = "JdbcCache.connection_string"; private static final String PASSWORD_KEY = "JdbcCache.password"; @@ -73,30 +76,58 @@ public static synchronized Connection getConnection() throws SQLException { @Override public void flush() throws IOException { try { - Configuration config = Application.getConfiguration(); - Connection conn = getConnection(); - int numDeletedImages, numDeletedDimensions; + int numDeletedImages = flushImages(); + int numDeletedInfos = flushInfos(); + logger.info("Deleted {} cached image(s) and {} cached dimension(s)", + numDeletedImages, numDeletedInfos); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } - // delete the contents of the image table - final String imageTableName = config.getString(IMAGE_TABLE_KEY); - if (imageTableName != null && imageTableName.length() > 0) { - String sql = "DELETE FROM " + imageTableName; - PreparedStatement statement = conn.prepareStatement(sql); - numDeletedImages = statement.executeUpdate(); - } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); - } + /** + * @return The number of flushed images + * @throws SQLException + * @throws IOException + */ + private int flushImages() throws SQLException, IOException { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); - // delete the contents of the info table - final String infoTableName = config.getString(INFO_TABLE_KEY); - if (infoTableName != null && infoTableName.length() > 0) { - String sql = "DELETE FROM " + infoTableName; - PreparedStatement statement = conn.prepareStatement(sql); - numDeletedDimensions = statement.executeUpdate(); - } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); - } + final String imageTableName = config.getString(IMAGE_TABLE_KEY); + if (imageTableName != null && imageTableName.length() > 0) { + String sql = "DELETE FROM " + imageTableName; + PreparedStatement statement = conn.prepareStatement(sql); + return statement.executeUpdate(); + } else { + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + } + /** + * @return The number of flushed infos + * @throws SQLException + * @throws IOException + */ + private int flushInfos() throws SQLException, IOException { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); + + final String infoTableName = config.getString(INFO_TABLE_KEY); + if (infoTableName != null && infoTableName.length() > 0) { + String sql = "DELETE FROM " + infoTableName; + PreparedStatement statement = conn.prepareStatement(sql); + return statement.executeUpdate(); + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + } + + @Override + public void flush(Parameters params) throws IOException { + try { + int numDeletedImages = flushImage(params); + int numDeletedDimensions = flushInfo(params.getIdentifier()); logger.info("Deleted {} cached image(s) and {} cached dimension(s)", numDeletedImages, numDeletedDimensions); } catch (SQLException e) { @@ -104,78 +135,106 @@ public void flush() throws IOException { } } - @Override - public void flush(Parameters params) throws IOException { + /** + * @param params + * @return The number of flushed images + * @throws SQLException + * @throws IOException + */ + private int flushImage(Parameters params) throws SQLException, IOException { Configuration config = Application.getConfiguration(); - String tableName = config.getString(TABLE_NAME_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - Connection conn = getConnection(); - String sql = String.format("DELETE FROM %s WHERE identifier = ?", - tableName); - PreparedStatement statement = conn.prepareStatement(sql); - statement.setString(1, params.getIdentifier().getValue()); - int affectedRows = statement.executeUpdate(); - logger.info("Deleted {} cached image(s)", affectedRows); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); - } + Connection conn = getConnection(); + + final String imageTableName = config.getString(IMAGE_TABLE_KEY); + if (imageTableName != null && imageTableName.length() > 0) { + String sql = String.format("DELETE FROM %s WHERE %s = ?", + imageTableName, IMAGE_TABLE_PARAMS_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setString(1, params.toString()); + return statement.executeUpdate(); } else { - throw new IOException(TABLE_NAME_KEY + " is not set"); + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + } + + /** + * @param identifier + * @return The number of flushed infos + * @throws SQLException + * @throws IOException + */ + private int flushInfo(Identifier identifier) throws SQLException, IOException { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); + + final String infoTableName = config.getString(INFO_TABLE_KEY); + if (infoTableName != null && infoTableName.length() > 0) { + String sql = String.format("DELETE FROM %s WHERE %s = ?", + infoTableName, INFO_TABLE_IDENTIFIER_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setString(1, identifier.toString()); + return statement.executeUpdate(); + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); } } @Override public void flushExpired() throws IOException { try { - Configuration config = Application.getConfiguration(); - Connection conn = getConnection(); - int numDeletedImages, numDeletedDimensions; - final Date oldestDate = getOldestValidDate(); - - // delete from the image table - final String imageTableName = config.getString(IMAGE_TABLE_KEY); - if (imageTableName != null && imageTableName.length() > 0) { - String sql = String.format("DELETE FROM %s WHERE %s < ?", - imageTableName, LAST_MODIFIED_COLUMN); - PreparedStatement statement = conn.prepareStatement(sql); - statement.setDate(1, oldestDate); - numDeletedImages = statement.executeUpdate(); - } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); - } - - // delete from the info table - final String infoTableName = config.getString(INFO_TABLE_KEY); - if (infoTableName != null && infoTableName.length() > 0) { - String sql = String.format("DELETE FROM %s WHERE %s < ?", - infoTableName, LAST_MODIFIED_COLUMN); - PreparedStatement statement = conn.prepareStatement(sql); - statement.setDate(1, oldestDate); - numDeletedDimensions = statement.executeUpdate(); - } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); - } - + int numDeletedImages = flushExpiredImages(); + int numDeletedInfos = flushExpiredInfos(); logger.info("Deleted {} cached image(s) and {} cached dimension(s)", - numDeletedImages, numDeletedDimensions); + numDeletedImages, numDeletedInfos); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } } + private int flushExpiredImages() throws SQLException, IOException { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); + + final String imageTableName = config.getString(IMAGE_TABLE_KEY); + if (imageTableName != null && imageTableName.length() > 0) { + String sql = String.format("DELETE FROM %s WHERE %s < ?", + imageTableName, IMAGE_TABLE_LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setDate(1, oldestValidDate()); + return statement.executeUpdate(); + } else { + throw new IOException(IMAGE_TABLE_KEY + " is not set"); + } + } + + private int flushExpiredInfos() throws SQLException, IOException { + Configuration config = Application.getConfiguration(); + Connection conn = getConnection(); + + final String infoTableName = config.getString(INFO_TABLE_KEY); + if (infoTableName != null && infoTableName.length() > 0) { + String sql = String.format("DELETE FROM %s WHERE %s < ?", + infoTableName, INFO_TABLE_LAST_MODIFIED_COLUMN); + PreparedStatement statement = conn.prepareStatement(sql); + statement.setDate(1, oldestValidDate()); + return statement.executeUpdate(); + } else { + throw new IOException(INFO_TABLE_KEY + " is not set"); + } + } + @Override public Dimension getDimension(Identifier identifier) throws IOException { - final Date oldestDate = getOldestValidDate(); + final Date oldestDate = oldestValidDate(); Configuration config = Application.getConfiguration(); String tableName = config.getString(INFO_TABLE_KEY, ""); if (tableName != null && tableName.length() > 0) { try { - Connection conn = getConnection(); String sql = String.format( - "SELECT width, height FROM %s WHERE identifier = ? AND %s > ?", - tableName, LAST_MODIFIED_COLUMN); - PreparedStatement statement = conn.prepareStatement(sql); + "SELECT width, height FROM %s " + + "WHERE identifier = ? AND %s > ?", + tableName, INFO_TABLE_LAST_MODIFIED_COLUMN); + PreparedStatement statement = getConnection().prepareStatement(sql); statement.setString(1, identifier.getValue()); statement.setDate(2, oldestDate); ResultSet resultSet = statement.getResultSet(); @@ -189,12 +248,13 @@ public Dimension getDimension(Identifier identifier) throws IOException { } else { throw new IOException(INFO_TABLE_KEY + " is not set"); } + return null; } @Override public InputStream getImageInputStream(Parameters params) { InputStream inputStream = null; - final Date oldestDate = getOldestValidDate(); + final Date oldestDate = oldestValidDate(); Configuration config = Application.getConfiguration(); String tableName = config.getString(IMAGE_TABLE_KEY, ""); if (tableName != null && tableName.length() > 0) { @@ -202,10 +262,11 @@ public InputStream getImageInputStream(Parameters params) { Connection conn = getConnection(); String sql = String.format( "SELECT %s FROM %s WHERE %s = ? AND %s > ?", - IMAGE_COLUMN, tableName, IDENTIFIER_COLUMN, - LAST_MODIFIED_COLUMN); + IMAGE_TABLE_IMAGE_COLUMN, tableName, + IMAGE_TABLE_PARAMS_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); - statement.setString(1, params.getIdentifier().getValue()); + statement.setString(1, params.toString()); statement.setDate(2, oldestDate); ResultSet resultSet = statement.getResultSet(); if (resultSet.next()) { @@ -215,7 +276,7 @@ public InputStream getImageInputStream(Parameters params) { logger.error(e.getMessage(), e); } } else { - logger.error("{} is not set", INFO_TABLE_KEY); + logger.error("{} is not set", IMAGE_TABLE_KEY); } return inputStream; } @@ -228,16 +289,8 @@ public OutputStream getImageOutputStream(Parameters params) if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); - String sql = String.format("CREATE TABLE %s (" + - "id INTEGER NOT NULL PRIMARY KEY, " + - "%s VARCHAR(2048) NOT NULL, " + - "%s BLOB," + - "%s DATETIME)", - tableName, IDENTIFIER_COLUMN, IMAGE_COLUMN, - LAST_MODIFIED_COLUMN); - PreparedStatement statement = conn.prepareStatement(sql); - - statement.execute(); + Blob blob = conn.createBlob(); + return blob.setBinaryStream(0); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } @@ -252,13 +305,21 @@ private void createImageTable() throws IOException { if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); - String sql = String.format("CREATE TABLE %s (" + + String sql = String.format( + "IF (NOT EXISTS (" + + "SELECT * FROM INFORMATION_SCHEMA.TABLES " + + "WHERE TABLE_NAME = '%s')) " + + "BEGIN " + + "CREATE TABLE %s (" + "id INTEGER NOT NULL PRIMARY KEY, " + - "%s VARCHAR(2048) NOT NULL, " + - "%s BLOB," + - "%s DATETIME)", - tableName, IDENTIFIER_COLUMN, IMAGE_COLUMN, - LAST_MODIFIED_COLUMN); + "%s VARCHAR(4096) NOT NULL, " + + "%s BLOB, " + + "%s DATETIME) " + + "END", + tableName, tableName, + IMAGE_TABLE_PARAMS_COLUMN, + IMAGE_TABLE_IMAGE_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.execute(); } catch (SQLException e) { @@ -275,14 +336,21 @@ private void createInfoTable() throws IOException { if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); - String sql = String.format("CREATE TABLE %s (" + + String sql = String.format( + "IF (NOT EXISTS (" + + "SELECT * FROM INFORMATION_SCHEMA.TABLES " + + "WHERE TABLE_NAME = '%s')) " + + "BEGIN " + + "CREATE TABLE %s (" + "id INTEGER NOT NULL PRIMARY KEY, " + - "%s VARCHAR(2048) NOT NULL, " + + "%s VARCHAR(4096) NOT NULL, " + + "%s INTEGER, " + "%s INTEGER, " + - "%s INTEGER," + - "%s DATETIME)", - tableName, IDENTIFIER_COLUMN, WIDTH_COLUMN, - HEIGHT_COLUMN, LAST_MODIFIED_COLUMN); + "%s DATETIME) " + + "END", + tableName, tableName, INFO_TABLE_IDENTIFIER_COLUMN, + INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, + INFO_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.execute(); } catch (SQLException e) { @@ -293,7 +361,7 @@ private void createInfoTable() throws IOException { } } - private Date getOldestValidDate() { + private Date oldestValidDate() { Configuration config = Application.getConfiguration(); final Instant oldestInstant = Instant.now(). minus(Duration.ofSeconds(config.getLong(TTL_KEY, 0))); @@ -310,8 +378,8 @@ public void putDimension(Identifier identifier, Dimension dimension) Connection conn = getConnection(); String sql = String.format( "INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)", - tableName, IDENTIFIER_COLUMN, WIDTH_COLUMN, - HEIGHT_COLUMN); + tableName, INFO_TABLE_IDENTIFIER_COLUMN, INFO_TABLE_WIDTH_COLUMN, + INFO_TABLE_HEIGHT_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.setString(1, identifier.getValue()); statement.setInt(2, dimension.width); From 6014335728fe9a28110f26edf6a8de772cdf9d81 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Thu, 22 Oct 2015 18:28:31 -0500 Subject: [PATCH 03/24] Comment out KakaduProcessor.path_to_binaries --- cantaloupe.properties.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cantaloupe.properties.sample b/cantaloupe.properties.sample index f785643f3..53f84c6bf 100644 --- a/cantaloupe.properties.sample +++ b/cantaloupe.properties.sample @@ -151,7 +151,7 @@ Java2dProcessor.tif.reader = TIFFImageReader ### # Optional; overrides the PATH -KakaduProcessor.path_to_binaries = /usr/local/bin +#KakaduProcessor.path_to_binaries = /usr/local/bin # Due to a quirk of kdu_expand, you will need to create a symbolic link to # /dev/stdout somewhere called `stdout.ppm`, like this: From 8a3c47333b7e873ceaf43bc87bd38620930e9f8a Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Thu, 22 Oct 2015 19:13:06 -0500 Subject: [PATCH 04/24] Add administrative info to developer guide --- website/_includes/toc.html | 1 + website/developers.html | 43 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/website/_includes/toc.html b/website/_includes/toc.html index 3bffb8428..cc2fa1eb6 100644 --- a/website/_includes/toc.html +++ b/website/_includes/toc.html @@ -68,6 +68,7 @@
  • Custom Caches
  • Javadoc
  • Contributing
  • +
  • Administrative
  • Change Log diff --git a/website/developers.html b/website/developers.html index e238739f3..08effcc39 100644 --- a/website/developers.html +++ b/website/developers.html @@ -4,7 +4,7 @@

    Developer's Guide

    -

    Cantaloupe is a standard Maven project that should open right up in any Java IDE.

    +

    Cantaloupe is a standard Maven project that should open right up in any Java IDE. Simply use the same configuration file and VM options (e.g. -Dcantaloupe.config=...) as you would when running from a JAR.

    Custom Resolvers

    @@ -31,7 +31,9 @@

    Javadoc

    Contributing

    -

    Ideas, suggestions, feature requests, bug reports, and other kinds of feedback are welcome; contact the author, or submit an issue or pull request. +

    Ideas, suggestions, feature requests, bug reports, and other kinds of feedback are welcome; contact the author, or submit an issue or pull request.

    + +

    This project uses the Gitflow branching model. All changes should take place in feature branches ("feature/xxxx") that branch off from the main branch, "develop." They can then be cleanly integrated, by the release manager, into a particular release.

    The general process for contributing code changes is:

    @@ -42,3 +44,40 @@

    Contributing

  • Push to the branch (git push origin feature/my-new-feature)
  • Create a new Pull Request
  • + +

    Administrative

    + +

    (This section is for release managers only.)

    + +

    Website

    + +

    The Jekyll website compiler transforms the HTML pages in the website folder into a working static website. The build/deploy_website.rb script takes care of building the website, adding the current Javadoc to it, committing it, and deploying it to GitHub Pages.

    + +

    To merely preview the website without releasing it, cd into the website folder and run jekyll serve, then go to http://localhost:4000/ in a web browser.

    + +

    Versioning

    + +

    Cantaloupe roughly uses semantic versioning. Major releases (n) break backwards compatibility in a major way. Minor releases (n.n) either do not break it, or only in a minor/trivial way. Patch releases (n.n.n) are for bugfixes only.

    + +

    Note that the above statement covers public API (the HTTP endpoints) only. Internals may change significantly from release to release (though this is not really likely.)

    + +

    Because Cantaloupe is generally intended to provide stable APIs, major version increments should be expected to be rare.

    + +

    Releasing

    + +

    Currently, the release process is mostly manual. The steps involved are:

    + +
      +
    1. Finalize the code to be released, addressing any relevant milestone issues, TODOs, etc.
    2. +
    3. Ensure that the tests are current, comprehensive, and passing
    4. +
    5. Finalize the documentation, including the website, upgrade guide, and change log
    6. +
    7. Merge into "release/x.x"
    8. +
    9. Update the version in pom.xml and commit this change
    10. +
    11. Merge into "master"
    12. +
    13. Create the release .zip archive with mvn package
    14. +
    15. Check that the .zip archive is as expected
    16. +
    17. Tag the release: git tag -a {version} -m 'Tag {version}'
    18. +
    19. Push the code: git push origin master; git push origin release/x.x; git push --tags
    20. +
    21. Add the .zip archive and change log info to the release tag on GitHub
    22. +
    23. Deploy the updated website: build/deploy_website.rb
    24. +
    From 4228ac85fd0863ef65f1c846f53cba029f976572 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Thu, 22 Oct 2015 19:34:22 -0500 Subject: [PATCH 05/24] Add cache.client.enabled key to the configuration file --- cantaloupe.properties.sample | 3 + .../cantaloupe/resource/AbstractResource.java | 68 ++++++++++--------- .../cantaloupe/resource/ImageResource.java | 5 +- .../resource/InformationResource.java | 4 +- .../resource/ImageResourceTest.java | 22 +++++- .../resource/InformationResourceTest.java | 1 + .../resource/LandingResourceTest.java | 1 + .../cantaloupe/resource/ResourceTest.java | 1 + website/changes.html | 6 ++ website/client-caching.html | 6 +- website/getting-started.html | 6 ++ 11 files changed, 83 insertions(+), 40 deletions(-) diff --git a/cantaloupe.properties.sample b/cantaloupe.properties.sample index 53f84c6bf..366415a40 100644 --- a/cantaloupe.properties.sample +++ b/cantaloupe.properties.sample @@ -165,6 +165,9 @@ KakaduProcessor.post_processor = java2d # CACHING ############### +# Whether to enable the response Cache-Control header. +cache.client.enabled = true + # Customize the response Cache-Control header. (This may be overridden by # proxies.) Comment out to disable the Cache-Control header. cache.client.max_age = 2592000 diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java index 0be3b50a5..7243b862f 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java @@ -20,42 +20,48 @@ abstract class AbstractResource extends ServerResource { private static Logger logger = LoggerFactory. getLogger(AbstractResource.class); - protected static List getCacheDirectives() { + protected List getCacheDirectives() { List directives = new ArrayList<>(); try { Configuration config = Application.getConfiguration(); - String maxAge = config.getString("cache.client.max_age"); - if (maxAge != null && maxAge.length() > 0) { - directives.add(CacheDirective.maxAge(Integer.parseInt(maxAge))); - } - String sMaxAge = config.getString("cache.client.shared_max_age"); - if (sMaxAge != null && sMaxAge.length() > 0) { - directives.add(CacheDirective. - sharedMaxAge(Integer.parseInt(sMaxAge))); - } - if (config.getBoolean("cache.client.public")) { - directives.add(CacheDirective.publicInfo()); - } else if (config.getBoolean("cache.client.private")) { - directives.add(CacheDirective.privateInfo()); - } - if (config.getBoolean("cache.client.no_cache")) { - directives.add(CacheDirective.noCache()); - } - if (config.getBoolean("cache.client.no_store")) { - directives.add(CacheDirective.noStore()); - } - if (config.getBoolean("cache.client.must_revalidate")) { - directives.add(CacheDirective.mustRevalidate()); - } - if (config.getBoolean("cache.client.proxy_revalidate")) { - directives.add(CacheDirective.proxyMustRevalidate()); - } - if (config.getBoolean("cache.client.no_transform")) { - directives.add(CacheDirective.noTransform()); + boolean enabled = config.getBoolean("cache.client.enabled", false); + if (enabled) { + String maxAge = config.getString("cache.client.max_age"); + if (maxAge != null && maxAge.length() > 0) { + directives.add(CacheDirective.maxAge(Integer.parseInt(maxAge))); + } + String sMaxAge = config.getString("cache.client.shared_max_age"); + if (sMaxAge != null && sMaxAge.length() > 0) { + directives.add(CacheDirective. + sharedMaxAge(Integer.parseInt(sMaxAge))); + } + if (config.getBoolean("cache.client.public")) { + directives.add(CacheDirective.publicInfo()); + } else if (config.getBoolean("cache.client.private")) { + directives.add(CacheDirective.privateInfo()); + } + if (config.getBoolean("cache.client.no_cache")) { + directives.add(CacheDirective.noCache()); + } + if (config.getBoolean("cache.client.no_store")) { + directives.add(CacheDirective.noStore()); + } + if (config.getBoolean("cache.client.must_revalidate")) { + directives.add(CacheDirective.mustRevalidate()); + } + if (config.getBoolean("cache.client.proxy_revalidate")) { + directives.add(CacheDirective.proxyMustRevalidate()); + } + if (config.getBoolean("cache.client.no_transform")) { + directives.add(CacheDirective.noTransform()); + } + } else { + logger.info("Cache-Control headers are disabled. " + + "(cache.client.enabled = false)"); } } catch (NoSuchElementException e) { - logger.info("Cache-Control headers are disabled. " + - "Original error: {}", e.getMessage()); + logger.info("Cache-Control headers are invalid: {}", + e.getMessage()); } return directives; } diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java index df6422c90..68fb624be 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java @@ -189,13 +189,10 @@ private void doWrite(OutputStream outputStream, Cache cache) private static Logger logger = LoggerFactory.getLogger(ImageResource.class); - private static final List CACHE_DIRECTIVES = - getCacheDirectives(); - @Override protected void doInit() throws ResourceException { super.doInit(); - getResponseCacheDirectives().addAll(CACHE_DIRECTIVES); + getResponseCacheDirectives().addAll(getCacheDirectives()); } /** diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/InformationResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/InformationResource.java index 72bf3f636..a1688b299 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/InformationResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/InformationResource.java @@ -46,8 +46,6 @@ public class InformationResource extends AbstractResource { private static final Set SUPPORTED_SERVICE_FEATURES = new HashSet<>(); - private static final List CACHE_DIRECTIVES = - getCacheDirectives(); static { SUPPORTED_SERVICE_FEATURES.add(ServiceFeature.SIZE_BY_WHITELISTED); @@ -60,7 +58,7 @@ public class InformationResource extends AbstractResource { @Override protected void doInit() throws ResourceException { super.doInit(); - getResponseCacheDirectives().addAll(CACHE_DIRECTIVES); + getResponseCacheDirectives().addAll(getCacheDirectives()); } /** diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/ImageResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/ImageResourceTest.java index 7483a336a..1ba2b9a37 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/ImageResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/ImageResourceTest.java @@ -56,8 +56,9 @@ public void testBasicAuth() throws Exception { assertEquals(Status.SUCCESS_OK, client.getStatus()); } - public void testCacheHeaders() { + public void testCacheHeadersWhenCachingEnabled() { Configuration config = Application.getConfiguration(); + config.setProperty("cache.client.enabled", "true"); config.setProperty("cache.client.max_age", "1234"); config.setProperty("cache.client.shared_max_age", "4567"); config.setProperty("cache.client.public", "true"); @@ -89,6 +90,25 @@ public void testCacheHeaders() { } } + public void testCacheHeadersWhenCachingDisabled() { + Configuration config = Application.getConfiguration(); + config.setProperty("cache.client.enabled", "false"); + config.setProperty("cache.client.max_age", "1234"); + config.setProperty("cache.client.shared_max_age", "4567"); + config.setProperty("cache.client.public", "true"); + config.setProperty("cache.client.private", "false"); + config.setProperty("cache.client.no_cache", "false"); + config.setProperty("cache.client.no_store", "false"); + config.setProperty("cache.client.must_revalidate", "false"); + config.setProperty("cache.client.proxy_revalidate", "false"); + config.setProperty("cache.client.no_transform", "true"); + Application.setConfiguration(config); + + ClientResource client = getClientForUriPath("/jpg/full/full/0/default.jpg"); + client.get(); + assertEquals(0, client.getResponse().getCacheDirectives().size()); + } + /** * Tests that the Link header respects the generate_https_links * key in the configuration. diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/InformationResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/InformationResourceTest.java index 7f389ca70..56481613a 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/InformationResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/InformationResourceTest.java @@ -21,6 +21,7 @@ public class InformationResourceTest extends ResourceTest { public void testCacheHeaders() { Configuration config = Application.getConfiguration(); + config.setProperty("cache.client.enabled", "true"); config.setProperty("cache.client.max_age", "1234"); config.setProperty("cache.client.shared_max_age", "4567"); config.setProperty("cache.client.public", "false"); diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/LandingResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/LandingResourceTest.java index 2032d19cf..5846a0021 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/LandingResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/LandingResourceTest.java @@ -18,6 +18,7 @@ public class LandingResourceTest extends ResourceTest { public void testCacheHeaders() { Configuration config = Application.getConfiguration(); + config.setProperty("cache.client.enabled", "true"); config.setProperty("cache.client.max_age", "1234"); config.setProperty("cache.client.shared_max_age", "4567"); config.setProperty("cache.client.public", "false"); diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java index e2f0ebcdd..9ea7d240c 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java @@ -39,6 +39,7 @@ public static BaseConfiguration newConfiguration() { } public void setUp() throws Exception { + System.setProperty("java.awt.headless", "true"); Application.setConfiguration(newConfiguration()); Application.startServer(); } diff --git a/website/changes.html b/website/changes.html index 6beed0ec8..6dee49680 100644 --- a/website/changes.html +++ b/website/changes.html @@ -4,6 +4,12 @@

    Change Log

    +

    1.1

    + +
      +
    • Added a cache.client.enabled key to the config file.
    • +
    +

    1.0.1

      diff --git a/website/client-caching.html b/website/client-caching.html index 3ab04e975..a909bdb34 100644 --- a/website/client-caching.html +++ b/website/client-caching.html @@ -5,7 +5,11 @@

      Client-Side Caching

      The HTTP/1.1 Cache-Control response header is configurable via the -cache.client.* keys in the configuration file. The default settings look something like this:

      +cache.client.* keys in the configuration file. To enable or disable it, use the cache.client.enabled key:

      + +
      cache.client.enabled = true
      + +

      The default settings look something like this:

      cache.client.max_age = 2592000
       cache.client.shared_max_age =
      diff --git a/website/getting-started.html b/website/getting-started.html
      index 03ac13944..64709d348 100644
      --- a/website/getting-started.html
      +++ b/website/getting-started.html
      @@ -26,6 +26,12 @@ 

      Upgrading

      Upgrading is usually just a matter of downloading a new version and running it. Since instances are self-contained, new versions can run happily alongside existing ones, with each using its own config file. Sometimes there are backwards-incompatible changes to the configuration file structure, though, so check below to see if there is anything more to be done.

      +

      1.0.x to 1.1

      + +
        +
      • Add the cache.client.enabled key from the sample configuration.
      • +
      +

      1.0-beta3 to 1.0

        From c5e7ec9ab622660581a13696be5c8148ef6a79a7 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Thu, 22 Oct 2015 19:48:01 -0500 Subject: [PATCH 06/24] All test cases inherit from CantaloupeTestCase, in order to set some VM options globally --- .../library/cantaloupe/ApplicationTest.java | 3 +-- .../library/cantaloupe/CantaloupeTestCase.java | 18 ++++++++++++++++++ .../cantaloupe/cache/CacheFactoryTest.java | 4 ++-- .../cantaloupe/cache/FilesystemCacheTest.java | 4 ++-- .../{test => }/iiif/Iiif20ConformanceTest.java | 6 +++--- .../cantaloupe/image/SourceFormatTest.java | 4 ++-- .../processor/ProcessorFactoryTest.java | 4 ++-- .../processor/ProcessorFeatureTest.java | 4 ++-- .../cantaloupe/processor/ProcessorTest.java | 6 ++---- .../processor/ProcessorUtilTest.java | 4 ++-- .../cantaloupe/request/IdentifierTest.java | 4 ++-- .../cantaloupe/request/OutputFormatTest.java | 4 ++-- .../cantaloupe/request/ParametersTest.java | 4 ++-- .../cantaloupe/request/QualityTest.java | 4 ++-- .../library/cantaloupe/request/RegionTest.java | 4 ++-- .../cantaloupe/request/RotationTest.java | 4 ++-- .../library/cantaloupe/request/SizeTest.java | 4 ++-- .../resolver/FilesystemResolverTest.java | 4 ++-- .../cantaloupe/resolver/HttpResolverTest.java | 4 ++-- .../cantaloupe/resolver/JdbcResolverTest.java | 4 ++-- .../resolver/ResolverFactoryTest.java | 4 ++-- .../resource/ComplianceLevelTest.java | 4 ++-- .../cantaloupe/resource/ResourceTest.java | 5 ++--- .../resource/ServiceFeatureTest.java | 4 ++-- 24 files changed, 64 insertions(+), 50 deletions(-) create mode 100644 src/test/java/edu/illinois/library/cantaloupe/CantaloupeTestCase.java rename src/test/java/edu/illinois/library/cantaloupe/{test => }/iiif/Iiif20ConformanceTest.java (99%) diff --git a/src/test/java/edu/illinois/library/cantaloupe/ApplicationTest.java b/src/test/java/edu/illinois/library/cantaloupe/ApplicationTest.java index ea725458a..33a84a204 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/ApplicationTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/ApplicationTest.java @@ -1,6 +1,5 @@ package edu.illinois.library.cantaloupe; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.io.FileUtils; @@ -17,7 +16,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class ApplicationTest extends TestCase { +public class ApplicationTest extends CantaloupeTestCase { private static final Integer PORT = 34852; diff --git a/src/test/java/edu/illinois/library/cantaloupe/CantaloupeTestCase.java b/src/test/java/edu/illinois/library/cantaloupe/CantaloupeTestCase.java new file mode 100644 index 000000000..8978202c8 --- /dev/null +++ b/src/test/java/edu/illinois/library/cantaloupe/CantaloupeTestCase.java @@ -0,0 +1,18 @@ +package edu.illinois.library.cantaloupe; + +import junit.framework.TestCase; + +/** + *

        Base test case class for all Cantaloupe unit tests.

        + * + *

        This class is named CantaloupeTestCase and not just TestCase in order to + * prevent bugs stemming from mistakenly importing the wrong TestCase.

        + */ +public abstract class CantaloupeTestCase extends TestCase { + + static { + System.setProperty("java.awt.headless", "true"); + System.setProperty("com.sun.media.jai.disableMediaLib", "true"); + } + +} diff --git a/src/test/java/edu/illinois/library/cantaloupe/cache/CacheFactoryTest.java b/src/test/java/edu/illinois/library/cantaloupe/cache/CacheFactoryTest.java index 1c7b4af7a..11c14d975 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/cache/CacheFactoryTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/cache/CacheFactoryTest.java @@ -1,10 +1,10 @@ package edu.illinois.library.cantaloupe.cache; import edu.illinois.library.cantaloupe.Application; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import org.apache.commons.configuration.BaseConfiguration; -public class CacheFactoryTest extends TestCase { +public class CacheFactoryTest extends CantaloupeTestCase { public void testGetInstance() throws Exception { BaseConfiguration config = new BaseConfiguration(); diff --git a/src/test/java/edu/illinois/library/cantaloupe/cache/FilesystemCacheTest.java b/src/test/java/edu/illinois/library/cantaloupe/cache/FilesystemCacheTest.java index a515eb64a..38dd66c78 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/cache/FilesystemCacheTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/cache/FilesystemCacheTest.java @@ -2,10 +2,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.ImageInfo; import edu.illinois.library.cantaloupe.request.Identifier; import edu.illinois.library.cantaloupe.request.Parameters; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.io.FileUtils; import org.restlet.data.Reference; @@ -17,7 +17,7 @@ import java.io.IOException; import java.nio.file.Paths; -public class FilesystemCacheTest extends TestCase { +public class FilesystemCacheTest extends CantaloupeTestCase { File fixturePath; File imagePath; diff --git a/src/test/java/edu/illinois/library/cantaloupe/test/iiif/Iiif20ConformanceTest.java b/src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java similarity index 99% rename from src/test/java/edu/illinois/library/cantaloupe/test/iiif/Iiif20ConformanceTest.java rename to src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java index 48e7c73e0..9237a67f4 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/test/iiif/Iiif20ConformanceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java @@ -1,7 +1,8 @@ -package edu.illinois.library.cantaloupe.test.iiif; +package edu.illinois.library.cantaloupe.iiif; import com.fasterxml.jackson.databind.ObjectMapper; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.ImageServerApplication; import edu.illinois.library.cantaloupe.image.ImageInfo; import edu.illinois.library.cantaloupe.image.SourceFormat; @@ -9,7 +10,6 @@ import edu.illinois.library.cantaloupe.processor.ProcessorFactory; import edu.illinois.library.cantaloupe.request.Identifier; import edu.illinois.library.cantaloupe.request.OutputFormat; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import org.restlet.Client; import org.restlet.Context; @@ -39,7 +39,7 @@ * @see IIIF Image * API 2.0 */ -public class Iiif20ConformanceTest extends TestCase { +public class Iiif20ConformanceTest extends CantaloupeTestCase { private static final Identifier IMAGE = new Identifier("escher_lego.jpg"); private static final Integer PORT = 34852; diff --git a/src/test/java/edu/illinois/library/cantaloupe/image/SourceFormatTest.java b/src/test/java/edu/illinois/library/cantaloupe/image/SourceFormatTest.java index 4484b1835..f79c44777 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/image/SourceFormatTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/image/SourceFormatTest.java @@ -1,10 +1,10 @@ package edu.illinois.library.cantaloupe.image; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.request.Identifier; -import junit.framework.TestCase; import org.restlet.data.MediaType; -public class SourceFormatTest extends TestCase { +public class SourceFormatTest extends CantaloupeTestCase { public void testValues() { assertNotNull(SourceFormat.valueOf("BMP")); diff --git a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFactoryTest.java b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFactoryTest.java index 62fd0bb4c..2779e5864 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFactoryTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFactoryTest.java @@ -1,11 +1,11 @@ package edu.illinois.library.cantaloupe.processor; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; -public class ProcessorFactoryTest extends TestCase { +public class ProcessorFactoryTest extends CantaloupeTestCase { public void setUp() { BaseConfiguration config = new BaseConfiguration(); diff --git a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFeatureTest.java b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFeatureTest.java index 12de102c4..08c699b51 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFeatureTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorFeatureTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.processor; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; -public class ProcessorFeatureTest extends TestCase { +public class ProcessorFeatureTest extends CantaloupeTestCase { public void testValues() { assertNotNull(ProcessorFeature.valueOf("MIRRORING")); diff --git a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java index 11830366c..7ee97ae6f 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java @@ -1,10 +1,10 @@ package edu.illinois.library.cantaloupe.processor; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.OutputFormat; import edu.illinois.library.cantaloupe.request.Parameters; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import java.awt.Dimension; @@ -20,12 +20,10 @@ /** * Contains base tests common to all Processors. */ -public abstract class ProcessorTest extends TestCase { +public abstract class ProcessorTest extends CantaloupeTestCase { static { Application.setConfiguration(new BaseConfiguration()); - System.setProperty("com.sun.media.jai.disableMediaLib", "true"); - System.setProperty("java.awt.headless", "true"); } protected SourceFormat getAnySupportedSourceFormat(Processor processor) { diff --git a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorUtilTest.java b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorUtilTest.java index 59999f782..92d911d0f 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorUtilTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorUtilTest.java @@ -1,13 +1,13 @@ package edu.illinois.library.cantaloupe.processor; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.request.Region; import edu.illinois.library.cantaloupe.request.Rotation; import edu.illinois.library.cantaloupe.request.Size; -import junit.framework.TestCase; import java.awt.image.BufferedImage; -public class ProcessorUtilTest extends TestCase { +public class ProcessorUtilTest extends CantaloupeTestCase { public void testConvertToRgb() { // TODO: write this diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/IdentifierTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/IdentifierTest.java index edf2b9812..4c8423cf6 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/IdentifierTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/IdentifierTest.java @@ -1,9 +1,9 @@ package edu.illinois.library.cantaloupe.request; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import org.restlet.data.Reference; -public class IdentifierTest extends TestCase { +public class IdentifierTest extends CantaloupeTestCase { public void testEquals() { Identifier id1 = new Identifier("cats"); diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/OutputFormatTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/OutputFormatTest.java index 6351a8076..a221ffff7 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/OutputFormatTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/OutputFormatTest.java @@ -1,9 +1,9 @@ package edu.illinois.library.cantaloupe.request; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; -import junit.framework.TestCase; -public class OutputFormatTest extends TestCase { +public class OutputFormatTest extends CantaloupeTestCase { public void testValues() { assertNotNull(OutputFormat.valueOf("GIF")); diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java index 2196072d6..4fea7aea7 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.request; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; -public class ParametersTest extends TestCase { +public class ParametersTest extends CantaloupeTestCase { private Parameters parameters; diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/QualityTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/QualityTest.java index 01033e254..a68f4fc9b 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/QualityTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/QualityTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.request; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; -public class QualityTest extends TestCase { +public class QualityTest extends CantaloupeTestCase { public void testValues() { assertNotNull(Quality.valueOf("BITONAL")); diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/RegionTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/RegionTest.java index 76a1da347..6509d6980 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/RegionTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/RegionTest.java @@ -1,11 +1,11 @@ package edu.illinois.library.cantaloupe.request; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import java.awt.Dimension; import java.awt.Rectangle; -public class RegionTest extends TestCase { +public class RegionTest extends CantaloupeTestCase { private Region region; diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/RotationTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/RotationTest.java index 0b50f1553..46b430290 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/RotationTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/RotationTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.request; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; -public class RotationTest extends TestCase { +public class RotationTest extends CantaloupeTestCase { private Rotation rotation; diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/SizeTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/SizeTest.java index ef253eada..a602c8952 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/SizeTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/SizeTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.request; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; -public class SizeTest extends TestCase { +public class SizeTest extends CantaloupeTestCase { private Size size; diff --git a/src/test/java/edu/illinois/library/cantaloupe/resolver/FilesystemResolverTest.java b/src/test/java/edu/illinois/library/cantaloupe/resolver/FilesystemResolverTest.java index eb9fda5f5..459f85ec9 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resolver/FilesystemResolverTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resolver/FilesystemResolverTest.java @@ -1,9 +1,9 @@ package edu.illinois.library.cantaloupe.resolver; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.Identifier; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import java.io.File; @@ -12,7 +12,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class FilesystemResolverTest extends TestCase { +public class FilesystemResolverTest extends CantaloupeTestCase { private static final Identifier IDENTIFIER = new Identifier("escher_lego.jpg"); diff --git a/src/test/java/edu/illinois/library/cantaloupe/resolver/HttpResolverTest.java b/src/test/java/edu/illinois/library/cantaloupe/resolver/HttpResolverTest.java index 9a831980c..16d97b922 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resolver/HttpResolverTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resolver/HttpResolverTest.java @@ -1,15 +1,15 @@ package edu.illinois.library.cantaloupe.resolver; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.Identifier; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import java.io.FileNotFoundException; import java.io.IOException; -public class HttpResolverTest extends TestCase { +public class HttpResolverTest extends CantaloupeTestCase { private static final Identifier IMAGE = new Identifier("14405804_o1.jpg"); HttpResolver instance; diff --git a/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java b/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java index 2a53daf9a..4f2efbc1e 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java @@ -1,9 +1,9 @@ package edu.illinois.library.cantaloupe.resolver; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.Identifier; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import java.io.File; @@ -15,7 +15,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; -public class JdbcResolverTest extends TestCase { +public class JdbcResolverTest extends CantaloupeTestCase { private JdbcResolver instance; diff --git a/src/test/java/edu/illinois/library/cantaloupe/resolver/ResolverFactoryTest.java b/src/test/java/edu/illinois/library/cantaloupe/resolver/ResolverFactoryTest.java index a148f4c0e..d441506cd 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resolver/ResolverFactoryTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resolver/ResolverFactoryTest.java @@ -1,10 +1,10 @@ package edu.illinois.library.cantaloupe.resolver; import edu.illinois.library.cantaloupe.Application; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import org.apache.commons.configuration.BaseConfiguration; -public class ResolverFactoryTest extends TestCase { +public class ResolverFactoryTest extends CantaloupeTestCase { public void testGetResolver() throws Exception { BaseConfiguration config = new BaseConfiguration(); diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/ComplianceLevelTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/ComplianceLevelTest.java index 6f08403d9..c5342d2e1 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/ComplianceLevelTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/ComplianceLevelTest.java @@ -1,14 +1,14 @@ package edu.illinois.library.cantaloupe.resource; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.processor.ProcessorFeature; import edu.illinois.library.cantaloupe.request.OutputFormat; import edu.illinois.library.cantaloupe.request.Quality; -import junit.framework.TestCase; import java.util.HashSet; import java.util.Set; -public class ComplianceLevelTest extends TestCase { +public class ComplianceLevelTest extends CantaloupeTestCase { public void testGetLevel() { Set serviceFeatures = new HashSet<>(); diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java index 9ea7d240c..3617718c8 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/ResourceTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.resource; import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.ImageServerApplication; -import junit.framework.TestCase; import org.apache.commons.configuration.BaseConfiguration; import org.restlet.Client; import org.restlet.Context; @@ -14,7 +14,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public abstract class ResourceTest extends TestCase { +public abstract class ResourceTest extends CantaloupeTestCase { protected static final Integer PORT = 34852; @@ -39,7 +39,6 @@ public static BaseConfiguration newConfiguration() { } public void setUp() throws Exception { - System.setProperty("java.awt.headless", "true"); Application.setConfiguration(newConfiguration()); Application.startServer(); } diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/ServiceFeatureTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/ServiceFeatureTest.java index 5124b7492..78f70c4f2 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/ServiceFeatureTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/ServiceFeatureTest.java @@ -1,8 +1,8 @@ package edu.illinois.library.cantaloupe.resource; -import junit.framework.TestCase; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; -public class ServiceFeatureTest extends TestCase { +public class ServiceFeatureTest extends CantaloupeTestCase { public void testValues() { assertNotNull(ServiceFeature.valueOf("BASE_URI_REDIRECT")); From 6aee6a682fd9a240cf7b70430ee8d22abb451a90 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Mon, 26 Oct 2015 18:26:59 -0500 Subject: [PATCH 07/24] Change some log levels in getCacheDirectives() --- .../library/cantaloupe/resource/AbstractResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java index 7243b862f..8fd0b43c6 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java @@ -56,11 +56,11 @@ protected List getCacheDirectives() { directives.add(CacheDirective.noTransform()); } } else { - logger.info("Cache-Control headers are disabled. " + + logger.debug("Cache-Control headers are disabled. " + "(cache.client.enabled = false)"); } } catch (NoSuchElementException e) { - logger.info("Cache-Control headers are invalid: {}", + logger.warn("Cache-Control headers are invalid: {}", e.getMessage()); } return directives; From 01f3082b10ffaabfdf23b568ec6ec721f2ba7635 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Mon, 26 Oct 2015 19:28:57 -0500 Subject: [PATCH 08/24] Add fromUri() --- .../cantaloupe/request/Parameters.java | 70 +++++++++++++++++-- .../cantaloupe/request/ParametersTest.java | 23 ++++++ 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/request/Parameters.java b/src/main/java/edu/illinois/library/cantaloupe/request/Parameters.java index 6efb706b5..aac7590e6 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/request/Parameters.java +++ b/src/main/java/edu/illinois/library/cantaloupe/request/Parameters.java @@ -1,6 +1,7 @@ package edu.illinois.library.cantaloupe.request; import edu.illinois.library.cantaloupe.image.SourceFormat; +import org.apache.commons.lang3.StringUtils; /** * Encapsulates the parameters of an IIIF request. @@ -18,6 +19,41 @@ public class Parameters implements Comparable { private Size size; /** + * @param paramsStr URI path fragment beginning from the identifier onward + * @throws IllegalArgumentException if the params is not in + * the correct format + */ + public static Parameters fromUri(String paramsStr) + throws IllegalArgumentException { + Parameters params = new Parameters(); + String[] parts = StringUtils.split(paramsStr, "/"); + if (parts.length == 5) { + params.setIdentifier(Identifier.fromUri(parts[0])); + params.setRegion(Region.fromUri(parts[1])); + params.setSize(Size.fromUri(parts[2])); + params.setRotation(Rotation.fromUri(parts[3])); + String[] subparts = StringUtils.split(parts[4], "."); + if (subparts.length == 2) { + params.setQuality(Quality.valueOf(subparts[0].toUpperCase())); + params.setOutputFormat(OutputFormat.valueOf(subparts[1].toUpperCase())); + } else { + throw new IllegalArgumentException("Invalid parameters format"); + } + } else { + throw new IllegalArgumentException("Invalid parameters format"); + } + return params; + } + + /** + * No-op constructor. + */ + public Parameters() {} + + /** + + + /** * @param identifier From URI * @param region From URI * @param size From URI @@ -41,14 +77,14 @@ public int compareTo(Parameters params) { return (last == 0) ? this.toString().compareTo(params.toString()) : last; } - public OutputFormat getOutputFormat() { - return outputFormat; - } - public Identifier getIdentifier() { return identifier; } + public OutputFormat getOutputFormat() { + return outputFormat; + } + public Quality getQuality() { return quality; } @@ -65,6 +101,32 @@ public Size getSize() { return size; } + public void setIdentifier(Identifier identifier) { + this.identifier = identifier; + } + + public void setOutputFormat(OutputFormat outputFormat) { + this.outputFormat = outputFormat; + } + + public void setQuality(Quality quality) { + this.quality = quality; + } + + public void setRegion(Region region) { + this.region = region; + } + + public void setRotation(Rotation rotation) { + this.rotation = rotation; + } + + public void setSize(Size size) { + this.size = size; + } + + + /** * @return Whether the parameters are effectively requesting the unmodified * source image, i.e. whether they specify full region, full scale, 0 diff --git a/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java b/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java index 4fea7aea7..b0ad47ca1 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/request/ParametersTest.java @@ -11,6 +11,29 @@ public void setUp() { "default", "jpg"); } + public void testFromUri() { + parameters = Parameters.fromUri("bla/20,20,50,50/pct:90/15/bitonal.jpg"); + assertEquals("bla", parameters.getIdentifier().toString()); + assertEquals("20,20,50,50", parameters.getRegion().toString()); + assertEquals(90f, parameters.getSize().getPercent()); + assertEquals(15f, parameters.getRotation().getDegrees()); + assertEquals(Quality.BITONAL, parameters.getQuality()); + assertEquals(OutputFormat.JPG, parameters.getOutputFormat()); + + try { + Parameters.fromUri("bla/20,20,50,50/15/bitonal.jpg"); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // pass + } + try { + Parameters.fromUri("bla/20,20,50,50/pct:90/15/bitonal"); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // pass + } + } + public void testIsUnmodified() { parameters = new Parameters("identifier", "full", "full", "0", "default", "jpg"); From 42eaf22fbaa42275d02e4602920e7803175f0571 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Mon, 26 Oct 2015 20:46:40 -0500 Subject: [PATCH 09/24] WIP --- .../library/cantaloupe/cache/JdbcCache.java | 273 +++++++++++++----- .../cantaloupe/cache/JdbcCacheTest.java | 271 +++++++++++++++++ .../library/cantaloupe/test/TestUtil.java | 17 ++ 3 files changed, 492 insertions(+), 69 deletions(-) create mode 100644 src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java create mode 100644 src/test/java/edu/illinois/library/cantaloupe/test/TestUtil.java diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index efac9e91b..92e015669 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -8,18 +8,20 @@ import org.slf4j.LoggerFactory; import java.awt.Dimension; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.sql.Blob; import java.sql.Connection; -import java.sql.Date; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; +import java.util.Calendar; /** * Cache using a database table, storing images as BLOBs and image dimensions @@ -27,23 +29,81 @@ */ class JdbcCache implements Cache { + /** + * Buffers written image data and flushes it into a database tuple as a + * BLOB. + */ + private class JdbcImageOutputStream extends OutputStream { + + private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private Parameters params; + private Connection connection; + + public JdbcImageOutputStream(Connection conn, Parameters params) { + this.connection = conn; + this.params = params; + } + + @Override + public void close() throws IOException { + outputStream.close(); + } + + @Override + public void flush() throws IOException { + outputStream.flush(); + try { + Configuration config = Application.getConfiguration(); + String sql = String.format( + "INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)", + config.getString(IMAGE_TABLE_CONFIG_KEY), + IMAGE_TABLE_PARAMS_COLUMN, IMAGE_TABLE_IMAGE_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN); + PreparedStatement statement = this.connection.prepareStatement(sql); + statement.setString(1, this.params.toString()); + statement.setBinaryStream(2, + new ByteArrayInputStream(this.outputStream.toByteArray())); + statement.setTimestamp(3, now()); + statement.executeUpdate(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } + + @Override + public void write(int b) throws IOException { + outputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + outputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + outputStream.write(b, off, len); + } + + } + private static final Logger logger = LoggerFactory. getLogger(JdbcCache.class); - private static final String IMAGE_TABLE_IMAGE_COLUMN = "image"; - private static final String IMAGE_TABLE_LAST_MODIFIED_COLUMN = "last_modified"; - private static final String IMAGE_TABLE_PARAMS_COLUMN = "params"; - private static final String INFO_TABLE_HEIGHT_COLUMN = "height"; - private static final String INFO_TABLE_IDENTIFIER_COLUMN = "identifier"; - private static final String INFO_TABLE_LAST_MODIFIED_COLUMN = "last_modified"; - private static final String INFO_TABLE_WIDTH_COLUMN = "width"; - - private static final String CONNECTION_STRING_KEY = "JdbcCache.connection_string"; - private static final String PASSWORD_KEY = "JdbcCache.password"; - private static final String IMAGE_TABLE_KEY = "JdbcCache.image_table"; - private static final String INFO_TABLE_KEY = "JdbcCache.info_table"; - private static final String TTL_KEY = "JdbcCache.ttl_seconds"; - private static final String USER_KEY = "JdbcCache.user"; + public static final String IMAGE_TABLE_IMAGE_COLUMN = "image"; + public static final String IMAGE_TABLE_LAST_MODIFIED_COLUMN = "last_modified"; + public static final String IMAGE_TABLE_PARAMS_COLUMN = "params"; + public static final String INFO_TABLE_HEIGHT_COLUMN = "height"; + public static final String INFO_TABLE_IDENTIFIER_COLUMN = "identifier"; + public static final String INFO_TABLE_LAST_MODIFIED_COLUMN = "last_modified"; + public static final String INFO_TABLE_WIDTH_COLUMN = "width"; + + public static final String CONNECTION_STRING_CONFIG_KEY = "JdbcCache.connection_string"; + public static final String PASSWORD_CONFIG_KEY = "JdbcCache.password"; + public static final String IMAGE_TABLE_CONFIG_KEY = "JdbcCache.image_table"; + public static final String INFO_TABLE_CONFIG_KEY = "JdbcCache.info_table"; + public static final String TTL_CONFIG_KEY = "JdbcCache.ttl_seconds"; + public static final String USER_CONFIG_KEY = "JdbcCache.user"; private static Connection connection; @@ -64,9 +124,9 @@ public static synchronized Connection getConnection() throws SQLException { if (connection == null) { Configuration config = Application.getConfiguration(); final String connectionString = config. - getString(CONNECTION_STRING_KEY, ""); - final String user = config.getString(USER_KEY, ""); - final String password = config.getString(PASSWORD_KEY, ""); + getString(CONNECTION_STRING_CONFIG_KEY, ""); + final String user = config.getString(USER_CONFIG_KEY, ""); + final String password = config.getString(PASSWORD_CONFIG_KEY, ""); connection = DriverManager.getConnection(connectionString, user, password); } @@ -94,13 +154,13 @@ private int flushImages() throws SQLException, IOException { Configuration config = Application.getConfiguration(); Connection conn = getConnection(); - final String imageTableName = config.getString(IMAGE_TABLE_KEY); + final String imageTableName = config.getString(IMAGE_TABLE_CONFIG_KEY); if (imageTableName != null && imageTableName.length() > 0) { String sql = "DELETE FROM " + imageTableName; PreparedStatement statement = conn.prepareStatement(sql); return statement.executeUpdate(); } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } } @@ -113,13 +173,13 @@ private int flushInfos() throws SQLException, IOException { Configuration config = Application.getConfiguration(); Connection conn = getConnection(); - final String infoTableName = config.getString(INFO_TABLE_KEY); + final String infoTableName = config.getString(INFO_TABLE_CONFIG_KEY); if (infoTableName != null && infoTableName.length() > 0) { String sql = "DELETE FROM " + infoTableName; PreparedStatement statement = conn.prepareStatement(sql); return statement.executeUpdate(); } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } } @@ -145,7 +205,7 @@ private int flushImage(Parameters params) throws SQLException, IOException { Configuration config = Application.getConfiguration(); Connection conn = getConnection(); - final String imageTableName = config.getString(IMAGE_TABLE_KEY); + final String imageTableName = config.getString(IMAGE_TABLE_CONFIG_KEY); if (imageTableName != null && imageTableName.length() > 0) { String sql = String.format("DELETE FROM %s WHERE %s = ?", imageTableName, IMAGE_TABLE_PARAMS_COLUMN); @@ -153,7 +213,7 @@ private int flushImage(Parameters params) throws SQLException, IOException { statement.setString(1, params.toString()); return statement.executeUpdate(); } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } } @@ -167,7 +227,7 @@ private int flushInfo(Identifier identifier) throws SQLException, IOException { Configuration config = Application.getConfiguration(); Connection conn = getConnection(); - final String infoTableName = config.getString(INFO_TABLE_KEY); + final String infoTableName = config.getString(INFO_TABLE_CONFIG_KEY); if (infoTableName != null && infoTableName.length() > 0) { String sql = String.format("DELETE FROM %s WHERE %s = ?", infoTableName, INFO_TABLE_IDENTIFIER_COLUMN); @@ -175,7 +235,7 @@ private int flushInfo(Identifier identifier) throws SQLException, IOException { statement.setString(1, identifier.toString()); return statement.executeUpdate(); } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } } @@ -195,15 +255,15 @@ private int flushExpiredImages() throws SQLException, IOException { Configuration config = Application.getConfiguration(); Connection conn = getConnection(); - final String imageTableName = config.getString(IMAGE_TABLE_KEY); + final String imageTableName = config.getString(IMAGE_TABLE_CONFIG_KEY); if (imageTableName != null && imageTableName.length() > 0) { String sql = String.format("DELETE FROM %s WHERE %s < ?", imageTableName, IMAGE_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); - statement.setDate(1, oldestValidDate()); + statement.setTimestamp(1, oldestValidDate()); return statement.executeUpdate(); } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } } @@ -211,42 +271,44 @@ private int flushExpiredInfos() throws SQLException, IOException { Configuration config = Application.getConfiguration(); Connection conn = getConnection(); - final String infoTableName = config.getString(INFO_TABLE_KEY); + final String infoTableName = config.getString(INFO_TABLE_CONFIG_KEY); if (infoTableName != null && infoTableName.length() > 0) { String sql = String.format("DELETE FROM %s WHERE %s < ?", infoTableName, INFO_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); - statement.setDate(1, oldestValidDate()); + statement.setTimestamp(1, oldestValidDate()); return statement.executeUpdate(); } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } } @Override public Dimension getDimension(Identifier identifier) throws IOException { - final Date oldestDate = oldestValidDate(); + final Timestamp oldestDate = oldestValidDate(); Configuration config = Application.getConfiguration(); - String tableName = config.getString(INFO_TABLE_KEY, ""); + String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); if (tableName != null && tableName.length() > 0) { try { String sql = String.format( - "SELECT width, height FROM %s " + - "WHERE identifier = ? AND %s > ?", - tableName, INFO_TABLE_LAST_MODIFIED_COLUMN); + "SELECT %s, %s FROM %s WHERE %s = ? AND %s > ?", + INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, + tableName, INFO_TABLE_IDENTIFIER_COLUMN, + INFO_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = getConnection().prepareStatement(sql); statement.setString(1, identifier.getValue()); - statement.setDate(2, oldestDate); - ResultSet resultSet = statement.getResultSet(); + statement.setTimestamp(2, oldestDate); + ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { - return new Dimension(resultSet.getInt("width"), - resultSet.getInt("height")); + return new Dimension( + resultSet.getInt(INFO_TABLE_WIDTH_COLUMN), + resultSet.getInt(INFO_TABLE_HEIGHT_COLUMN)); } } catch (SQLException e) { throw new IOException(e.getMessage(), e); } } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } return null; } @@ -254,9 +316,9 @@ public Dimension getDimension(Identifier identifier) throws IOException { @Override public InputStream getImageInputStream(Parameters params) { InputStream inputStream = null; - final Date oldestDate = oldestValidDate(); + final Timestamp oldestDate = oldestValidDate(); Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_KEY, ""); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); @@ -267,8 +329,8 @@ public InputStream getImageInputStream(Parameters params) { IMAGE_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.setString(1, params.toString()); - statement.setDate(2, oldestDate); - ResultSet resultSet = statement.getResultSet(); + statement.setTimestamp(2, oldestDate); + ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { inputStream = resultSet.getBinaryStream(1); } @@ -276,7 +338,7 @@ public InputStream getImageInputStream(Parameters params) { logger.error(e.getMessage(), e); } } else { - logger.error("{} is not set", IMAGE_TABLE_KEY); + logger.error("{} is not set", IMAGE_TABLE_CONFIG_KEY); } return inputStream; } @@ -285,33 +347,36 @@ public InputStream getImageInputStream(Parameters params) { public OutputStream getImageOutputStream(Parameters params) throws IOException { Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_KEY, ""); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); if (tableName != null && tableName.length() > 0) { try { - Connection conn = getConnection(); - Blob blob = conn.createBlob(); - return blob.setBinaryStream(0); + return new JdbcImageOutputStream(getConnection(), params); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } } + public void createTables() throws IOException { + createImageTable(); + createInfoTable(); + } + private void createImageTable() throws IOException { Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_KEY, ""); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); + /* String sql = String.format( "IF (NOT EXISTS (" + "SELECT * FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_NAME = '%s')) " + "BEGIN " + "CREATE TABLE %s (" + - "id INTEGER NOT NULL PRIMARY KEY, " + "%s VARCHAR(4096) NOT NULL, " + "%s BLOB, " + "%s DATETIME) " + @@ -319,6 +384,15 @@ private void createImageTable() throws IOException { tableName, tableName, IMAGE_TABLE_PARAMS_COLUMN, IMAGE_TABLE_IMAGE_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN); */ + String sql = String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "%s VARCHAR(4096) NOT NULL, " + + "%s BLOB, " + + "%s DATETIME);", + tableName, + IMAGE_TABLE_PARAMS_COLUMN, + IMAGE_TABLE_IMAGE_COLUMN, IMAGE_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.execute(); @@ -326,23 +400,23 @@ private void createImageTable() throws IOException { throw new IOException(e.getMessage(), e); } } else { - throw new IOException(IMAGE_TABLE_KEY + " is not set"); + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } } private void createInfoTable() throws IOException { Configuration config = Application.getConfiguration(); - String tableName = config.getString(INFO_TABLE_KEY, ""); + String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); + /* String sql = String.format( "IF (NOT EXISTS (" + "SELECT * FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_NAME = '%s')) " + "BEGIN " + "CREATE TABLE %s (" + - "id INTEGER NOT NULL PRIMARY KEY, " + "%s VARCHAR(4096) NOT NULL, " + "%s INTEGER, " + "%s INTEGER, " + @@ -350,6 +424,15 @@ private void createInfoTable() throws IOException { "END", tableName, tableName, INFO_TABLE_IDENTIFIER_COLUMN, INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, + INFO_TABLE_LAST_MODIFIED_COLUMN); */ + String sql = String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "%s VARCHAR(4096) NOT NULL, " + + "%s INTEGER, " + + "%s INTEGER, " + + "%s DATETIME);", + tableName, INFO_TABLE_IDENTIFIER_COLUMN, + INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, INFO_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.execute(); @@ -357,39 +440,91 @@ private void createInfoTable() throws IOException { throw new IOException(e.getMessage(), e); } } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); + } + } + + public void dropTables() throws IOException { + dropImageTable(); + dropInfoTable(); + } + + private void dropImageTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = "DROP TABLE " + tableName; + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); + } + } + + private void dropInfoTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = "DROP TABLE " + tableName; + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } } - private Date oldestValidDate() { + private Timestamp now() { + Calendar calendar = Calendar.getInstance(); + java.util.Date now = calendar.getTime(); + return new java.sql.Timestamp(now.getTime()); + } + + public Timestamp oldestValidDate() { Configuration config = Application.getConfiguration(); - final Instant oldestInstant = Instant.now(). - minus(Duration.ofSeconds(config.getLong(TTL_KEY, 0))); - return new Date(Date.from(oldestInstant).getTime()); + long ttl = config.getLong(TTL_CONFIG_KEY, 0); + if (ttl > 0) { + final Instant oldestInstant = Instant.now(). + minus(Duration.ofSeconds(ttl)); + return Timestamp.from(oldestInstant); + } else { + return new Timestamp(Long.MIN_VALUE); + } } @Override public void putDimension(Identifier identifier, Dimension dimension) throws IOException { Configuration config = Application.getConfiguration(); - String tableName = config.getString(INFO_TABLE_KEY, ""); + String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); if (tableName != null && tableName.length() > 0) { try { Connection conn = getConnection(); String sql = String.format( - "INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)", - tableName, INFO_TABLE_IDENTIFIER_COLUMN, INFO_TABLE_WIDTH_COLUMN, - INFO_TABLE_HEIGHT_COLUMN); + "INSERT INTO %s (%s, %s, %s, %s) VALUES (?, ?, ?, ?)", + tableName, INFO_TABLE_IDENTIFIER_COLUMN, + INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, + INFO_TABLE_LAST_MODIFIED_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.setString(1, identifier.getValue()); statement.setInt(2, dimension.width); statement.setInt(3, dimension.height); - statement.execute(); + statement.setTimestamp(4, now()); + statement.executeUpdate(); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } } else { - throw new IOException(INFO_TABLE_KEY + " is not set"); + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } } diff --git a/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java b/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java new file mode 100644 index 000000000..3e3827ab7 --- /dev/null +++ b/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java @@ -0,0 +1,271 @@ +package edu.illinois.library.cantaloupe.cache; + +import edu.illinois.library.cantaloupe.Application; +import edu.illinois.library.cantaloupe.CantaloupeTestCase; +import edu.illinois.library.cantaloupe.request.Identifier; +import edu.illinois.library.cantaloupe.request.Parameters; +import edu.illinois.library.cantaloupe.test.TestUtil; +import org.apache.commons.configuration.BaseConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.io.IOUtils; + +import java.awt.Dimension; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.time.Duration; +import java.time.Instant; + +public class JdbcCacheTest extends CantaloupeTestCase { + + JdbcCache instance; + + public void setUp() throws Exception { + BaseConfiguration config = new BaseConfiguration(); + // use an in-memory H2 database + config.setProperty(JdbcCache.CONNECTION_STRING_CONFIG_KEY, + "jdbc:h2:mem:test"); + config.setProperty(JdbcCache.USER_CONFIG_KEY, "sa"); + config.setProperty(JdbcCache.PASSWORD_CONFIG_KEY, ""); + config.setProperty(JdbcCache.IMAGE_TABLE_CONFIG_KEY, "image_cache"); + config.setProperty(JdbcCache.INFO_TABLE_CONFIG_KEY, "info_cache"); + config.setProperty(JdbcCache.TTL_CONFIG_KEY, 0); + Application.setConfiguration(config); + + instance = new JdbcCache(); + + // persist some images + Parameters params = new Parameters("cats", "full", "full", "0", + "default", "jpg"); + OutputStream os = instance.getImageOutputStream(params); + IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); + os.flush(); + + params = new Parameters("dogs", "50,50,50,50", "pct:90", + "0", "default", "jpg"); + os = instance.getImageOutputStream(params); + IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); + os.flush(); + + params = new Parameters("bunnies", "10,20,50,90", "40,", + "15", "color", "png"); + os = instance.getImageOutputStream(params); + IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); + os.flush(); + + // persist some corresponding dimensions + instance.putDimension(new Identifier("cats"), new Dimension(50, 40)); + instance.putDimension(new Identifier("dogs"), new Dimension(500, 300)); + instance.putDimension(new Identifier("bunnies"), new Dimension(350, 240)); + + // assert that the data has been seeded + String sql = String.format("SELECT COUNT(%s) AS count FROM %s;", + JdbcCache.IMAGE_TABLE_PARAMS_COLUMN, + config.getString(JdbcCache.IMAGE_TABLE_CONFIG_KEY)); + PreparedStatement statement = JdbcCache.getConnection().prepareStatement(sql); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + assertEquals(3, resultSet.getInt("count")); + } else { + fail(); + } + + sql = String.format("SELECT COUNT(%s) AS count FROM %s;", + JdbcCache.INFO_TABLE_IDENTIFIER_COLUMN, + config.getString(JdbcCache.INFO_TABLE_CONFIG_KEY)); + statement = JdbcCache.getConnection().prepareStatement(sql); + resultSet = statement.executeQuery(); + if (resultSet.next()) { + assertEquals(3, resultSet.getInt("count")); + } else { + fail(); + } + } + + /** + * Clears the persistent store. + */ + public void tearDown() throws IOException { + instance.flush(); + } + + public void testFlush() throws Exception { + Configuration config = Application.getConfiguration(); + + instance.flush(); + + // assert that the images and infos were flushed + String sql = String.format("SELECT COUNT(%s) AS count FROM %s", + JdbcCache.IMAGE_TABLE_PARAMS_COLUMN, + config.getString(JdbcCache.IMAGE_TABLE_CONFIG_KEY)); + PreparedStatement statement = JdbcCache.getConnection().prepareStatement(sql); + ResultSet resultSet = statement.executeQuery(); + resultSet.next(); + assertEquals(0, resultSet.getInt("count")); + + sql = String.format("SELECT COUNT(%s) AS count FROM %s", + JdbcCache.INFO_TABLE_IDENTIFIER_COLUMN, + config.getString(JdbcCache.INFO_TABLE_CONFIG_KEY)); + statement = JdbcCache.getConnection().prepareStatement(sql); + resultSet = statement.executeQuery(); + resultSet.next(); + assertEquals(0, resultSet.getInt("count")); + } + + public void testFlushWithParameters() throws Exception { + Parameters params = new Parameters("cats", "full", "full", "0", + "default", "jpg"); + instance.flush(params); + + Configuration config = Application.getConfiguration(); + + // assert that the image and info were flushed + String sql = String.format("SELECT COUNT(%s) AS count FROM %s", + JdbcCache.IMAGE_TABLE_PARAMS_COLUMN, + config.getString(JdbcCache.IMAGE_TABLE_CONFIG_KEY)); + PreparedStatement statement = JdbcCache.getConnection().prepareStatement(sql); + ResultSet resultSet = statement.executeQuery(); + resultSet.next(); + assertEquals(2, resultSet.getInt("count")); + + sql = String.format("SELECT COUNT(%s) AS count FROM %s", + JdbcCache.INFO_TABLE_IDENTIFIER_COLUMN, + config.getString(JdbcCache.INFO_TABLE_CONFIG_KEY)); + statement = JdbcCache.getConnection().prepareStatement(sql); + resultSet = statement.executeQuery(); + resultSet.next(); + assertEquals(2, resultSet.getInt("count")); + } + + public void testFlushExpired() throws Exception { + Application.getConfiguration().setProperty(JdbcCache.TTL_CONFIG_KEY, 1); + + // wait for the seed data to invalidate + Thread.sleep(1500); + + // add some fresh entities + Parameters params = new Parameters("bees", "full", "full", "0", + "default", "jpg"); + OutputStream os = instance.getImageOutputStream(params); + IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); + os.flush(); + instance.putDimension(new Identifier("bees"), new Dimension(50, 40)); + + instance.flushExpired(); + + // assert that only the expired images and infos were flushed + Configuration config = Application.getConfiguration(); + String sql = String.format("SELECT COUNT(%s) AS count FROM %s", + JdbcCache.IMAGE_TABLE_PARAMS_COLUMN, + config.getString(JdbcCache.IMAGE_TABLE_CONFIG_KEY)); + PreparedStatement statement = JdbcCache.getConnection().prepareStatement(sql); + ResultSet resultSet = statement.executeQuery(); + resultSet.next(); + assertEquals(1, resultSet.getInt("count")); + + sql = String.format("SELECT COUNT(%s) AS count FROM %s", + JdbcCache.IMAGE_TABLE_PARAMS_COLUMN, + config.getString(JdbcCache.IMAGE_TABLE_CONFIG_KEY)); + statement = JdbcCache.getConnection().prepareStatement(sql); + resultSet = statement.executeQuery(); + resultSet.next(); + assertEquals(1, resultSet.getInt("count")); + } + + public void testGetDimensionWithZeroTtl() throws IOException { + // existing image + try { + Dimension actual = instance.getDimension(new Identifier("cats")); + Dimension expected = new Dimension(50, 40); + assertEquals(actual, expected); + } catch (IOException e) { + fail(); + } + // nonexistent image + assertNull(instance.getDimension(new Identifier("bogus"))); + } + + public void testGetDimensionWithNonZeroTtl() throws Exception { + Application.getConfiguration().setProperty(JdbcCache.TTL_CONFIG_KEY, 1); + + // wait for the seed data to invalidate + Thread.sleep(1500); + + // add some fresh entities + Parameters params = new Parameters("bees", "full", "full", "0", + "default", "jpg"); + IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), + instance.getImageOutputStream(params)); + instance.putDimension(new Identifier("bees"), new Dimension(50, 40)); + + // existing, non-expired image + try { + Dimension actual = instance.getDimension(new Identifier("bees")); + Dimension expected = new Dimension(50, 40); + assertEquals(actual, expected); + } catch (IOException e) { + fail(); + } + // existing, expired image + assertNull(instance.getDimension(new Identifier("cats"))); + // nonexistent image + assertNull(instance.getDimension(new Identifier("bogus"))); + } + + public void testGetImageInputStreamWithZeroTtl() { + Parameters params = new Parameters("cats", "full", "full", "0", + "default", "jpg"); + assertNotNull(instance.getImageInputStream(params)); + } + + public void testGetImageInputStreamWithNonzeroTtl() throws Exception { + Application.getConfiguration().setProperty(JdbcCache.TTL_CONFIG_KEY, 1); + + // wait for the seed data to invalidate + Thread.sleep(1500); + + // add some fresh entities + Parameters params = new Parameters("bees", "full", "full", "0", + "default", "jpg"); + OutputStream os = instance.getImageOutputStream(params); + IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); + os.flush(); + instance.putDimension(new Identifier("bees"), new Dimension(50, 40)); + + // existing, non-expired image + assertNotNull(instance.getImageInputStream(params)); + // existing, expired image + assertNull(instance.getImageInputStream( + Parameters.fromUri("cats/full/full/0/default.jpg"))); + // nonexistent image + assertNull(instance.getImageInputStream( + Parameters.fromUri("bogus/full/full/0/default.jpg"))); + } + + public void testGetImageOutputStream() throws Exception { + Parameters params = new Parameters("cats", "full", "full", "0", + "default", "jpg"); + assertNotNull(instance.getImageOutputStream(params)); + } + + public void testOldestValidDate() { + // ttl = 0 + assertEquals(new Date(Long.MIN_VALUE), instance.oldestValidDate()); + // ttl = 50 + Application.getConfiguration().setProperty(JdbcCache.TTL_CONFIG_KEY, 50); + long expectedTime = Date.from(Instant.now().minus(Duration.ofSeconds(50))).getTime(); + long actualTime = instance.oldestValidDate().getTime(); + assertTrue(Math.abs(actualTime - expectedTime) < 100); + } + + public void testPutDimension() throws IOException { + Identifier identifier = new Identifier("birds"); + Dimension dimension = new Dimension(52, 52); + instance.putDimension(identifier, dimension); + assertEquals(dimension, instance.getDimension(identifier)); + } + +} diff --git a/src/test/java/edu/illinois/library/cantaloupe/test/TestUtil.java b/src/test/java/edu/illinois/library/cantaloupe/test/TestUtil.java new file mode 100644 index 000000000..3da47fca7 --- /dev/null +++ b/src/test/java/edu/illinois/library/cantaloupe/test/TestUtil.java @@ -0,0 +1,17 @@ +package edu.illinois.library.cantaloupe.test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public abstract class TestUtil { + + public static File getFixture(String filename) throws IOException { + File directory = new File("."); + String cwd = directory.getCanonicalPath(); + Path testPath = Paths.get(cwd, "src", "test", "resources"); + return new File(testPath + File.separator + filename); + } + +} \ No newline at end of file From 6cd78a8c43d20ed715e81ebb9f6cd9fe20f893f1 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 12:28:30 -0500 Subject: [PATCH 10/24] Document JdbcCache --- cantaloupe.properties.sample | 7 ++++++- website/_includes/toc.html | 1 + website/caches.html | 30 +++++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/cantaloupe.properties.sample b/cantaloupe.properties.sample index bb6be984f..7994cf5df 100644 --- a/cantaloupe.properties.sample +++ b/cantaloupe.properties.sample @@ -202,9 +202,14 @@ FilesystemCache.ttl_seconds = 2592000 JdbcCache.connection_string = jdbc:postgresql://localhost:5432/cantaloupe JdbcCache.user = alexd JdbcCache.password = + +# These will be created automatically. JdbcCache.image_table = image_cache JdbcCache.info_table = info_cache -JdbcCache.ttl_seconds = 10 + +# Time before a cached image becomes stale and needs to be reloaded. Set to +# blank or 0 for "forever" +JdbcCache.ttl_seconds = 2592000 ############### # LOGGING diff --git a/website/_includes/toc.html b/website/_includes/toc.html index cc2fa1eb6..27a3e15d3 100644 --- a/website/_includes/toc.html +++ b/website/_includes/toc.html @@ -39,6 +39,7 @@ Caches diff --git a/website/caches.html b/website/caches.html index b6178ea51..ef8e1628b 100644 --- a/website/caches.html +++ b/website/caches.html @@ -10,7 +10,35 @@

        Caches

        FilesystemCache

        -

        FilesystemCache caches generated images and (parts of) information requests into a filesystem directory. The location of this directory is configurable, as is the "time-to-live" of the cache files.

        +

        FilesystemCache caches generated images and (parts of) information requests into a filesystem directory. The location of this directory is configurable, as is the "time-to-live" of the cache files, with the following options:

        + +
        +
        FilesystemCache.pathname
        +
        Absolute path of the folder in which cached images and information responses will be stored.
        +
        FilesystemCache.ttl_seconds
        +
        Time-to-live of the cached images and information responses, in seconds.
        +
        + +

        JdbcCache

        + +

        JdbcCache caches generated images and (parts of) information requests into tables in a relational database (RDBMS). This cache can be configured with the following options:

        + +
        +
        JdbcCache.connection_string
        +
        A valid JDBC connection string; for example, jdbc:postgresql://localhost:5432/mydatabase.
        +
        JdbcCache.user
        +
        User to connect to the database as.
        +
        JdbcCache.password
        +
        Password to use when connecting to the database. Can be left blank if not needed.
        +
        JdbcCache.image_table
        +
        The table in which to cache images. This will be created automatically.
        +
        JdbcCache.info_table
        +
        The table in which to cache information responses. This will be created automatically.
        +
        JdbcCache.ttl_seconds
        +
        Time-to-live of the cached images and information responses, in seconds.
        +
        + +

        Note: JdbcCache has been tested with PostgreSQL 9.4 and H2 1.4. Other databases may work, but are untested. In particular, the database needs to support CREATE TABLE IF NOT EXISTS syntax, which in the case of PostgreSQL, arrived in version 9.1.

        Flushing the Cache

        From 27989b606c34f8e572f02d6d551695f19a2d8fe4 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 12:28:46 -0500 Subject: [PATCH 11/24] WIP --- .../library/cantaloupe/cache/Cache.java | 4 +- .../library/cantaloupe/cache/JdbcCache.java | 278 +++++++++--------- 2 files changed, 146 insertions(+), 136 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java index 6d02ebcad..c75e82c72 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java @@ -56,8 +56,8 @@ public interface Cache { /** * @param params IIIF request parameters - * @return OutputStream pointed at the cache to which an image - * corresponding to the supplied parameters can be written. + * @return OutputStream to which an image corresponding to the supplied + * parameters can be written. * @throws IOException */ OutputStream getImageOutputStream(Parameters params) throws IOException; diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index 92e015669..0c9062c36 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -115,11 +115,136 @@ public void write(byte[] b, int off, int len) throws IOException { Configuration config = Application.getConfiguration(); logger.info("Connection string: {}", config.getString("JdbcCache.connection_string")); - } catch (SQLException e) { + createTables(); + } catch (IOException | SQLException e) { logger.error("Failed to establish a database connection", e); } } + public static synchronized void createTables() throws IOException { + createImageTable(); + createInfoTable(); + } + + private static void createImageTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql; + if (conn.getMetaData().getDriverName().toLowerCase(). + contains("postgresql")) { + sql = String.format("CREATE TABLE IF NOT EXISTS %s (" + + "%s VARCHAR(4096) NOT NULL, " + + "%s BYTEA, " + + "%s TIMESTAMP);", + tableName, + IMAGE_TABLE_PARAMS_COLUMN, + IMAGE_TABLE_IMAGE_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN); + } else { + sql = String.format("CREATE TABLE IF NOT EXISTS %s (" + + "%s VARCHAR(4096) NOT NULL, " + + "%s BLOB, " + + "%s DATETIME);", + tableName, + IMAGE_TABLE_PARAMS_COLUMN, + IMAGE_TABLE_IMAGE_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN); + } + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + logger.info("Created table: {}", tableName); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); + } + } + + private static void createInfoTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql; + if (conn.getMetaData().getDriverName().toLowerCase(). + contains("postgresql")) { + sql = String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "%s VARCHAR(4096) NOT NULL, " + + "%s INTEGER, " + + "%s INTEGER, " + + "%s TIMESTAMP);", + tableName, INFO_TABLE_IDENTIFIER_COLUMN, + INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, + INFO_TABLE_LAST_MODIFIED_COLUMN); + } else { + sql = String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "%s VARCHAR(4096) NOT NULL, " + + "%s INTEGER, " + + "%s INTEGER, " + + "%s DATETIME);", + tableName, INFO_TABLE_IDENTIFIER_COLUMN, + INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, + INFO_TABLE_LAST_MODIFIED_COLUMN); + } + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + logger.info("Created table: {}", tableName); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); + } + } + + public static synchronized void dropTables() throws IOException { + dropImageTable(); + dropInfoTable(); + } + + private static void dropImageTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = "DROP TABLE " + tableName; + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + logger.info("Dropped table: {}", tableName); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); + } + } + + private static void dropInfoTable() throws IOException { + Configuration config = Application.getConfiguration(); + String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + Connection conn = getConnection(); + String sql = "DROP TABLE " + tableName; + PreparedStatement statement = conn.prepareStatement(sql); + statement.execute(); + logger.info("Dropped table: {}", tableName); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); + } + } + public static synchronized Connection getConnection() throws SQLException { if (connection == null) { Configuration config = Application.getConfiguration(); @@ -300,9 +425,12 @@ public Dimension getDimension(Identifier identifier) throws IOException { statement.setTimestamp(2, oldestDate); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { + logger.debug("Hit for dimension: {}", identifier); return new Dimension( resultSet.getInt(INFO_TABLE_WIDTH_COLUMN), resultSet.getInt(INFO_TABLE_HEIGHT_COLUMN)); + } else { + flushInfo(identifier); } } catch (SQLException e) { throw new IOException(e.getMessage(), e); @@ -332,6 +460,7 @@ public InputStream getImageInputStream(Parameters params) { statement.setTimestamp(2, oldestDate); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { + logger.debug("Hit for image: {}", params); inputStream = resultSet.getBinaryStream(1); } } catch (SQLException e) { @@ -346,141 +475,21 @@ public InputStream getImageInputStream(Parameters params) { @Override public OutputStream getImageOutputStream(Parameters params) throws IOException { - Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - return new JdbcImageOutputStream(getConnection(), params); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); - } - } else { - throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); - } - } - - public void createTables() throws IOException { - createImageTable(); - createInfoTable(); - } - - private void createImageTable() throws IOException { - Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - Connection conn = getConnection(); - /* - String sql = String.format( - "IF (NOT EXISTS (" + - "SELECT * FROM INFORMATION_SCHEMA.TABLES " + - "WHERE TABLE_NAME = '%s')) " + - "BEGIN " + - "CREATE TABLE %s (" + - "%s VARCHAR(4096) NOT NULL, " + - "%s BLOB, " + - "%s DATETIME) " + - "END", - tableName, tableName, - IMAGE_TABLE_PARAMS_COLUMN, - IMAGE_TABLE_IMAGE_COLUMN, - IMAGE_TABLE_LAST_MODIFIED_COLUMN); */ - String sql = String.format( - "CREATE TABLE IF NOT EXISTS %s (" + - "%s VARCHAR(4096) NOT NULL, " + - "%s BLOB, " + - "%s DATETIME);", - tableName, - IMAGE_TABLE_PARAMS_COLUMN, - IMAGE_TABLE_IMAGE_COLUMN, - IMAGE_TABLE_LAST_MODIFIED_COLUMN); - PreparedStatement statement = conn.prepareStatement(sql); - statement.execute(); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); - } - } else { - throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); - } - } - - private void createInfoTable() throws IOException { - Configuration config = Application.getConfiguration(); - String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - Connection conn = getConnection(); - /* - String sql = String.format( - "IF (NOT EXISTS (" + - "SELECT * FROM INFORMATION_SCHEMA.TABLES " + - "WHERE TABLE_NAME = '%s')) " + - "BEGIN " + - "CREATE TABLE %s (" + - "%s VARCHAR(4096) NOT NULL, " + - "%s INTEGER, " + - "%s INTEGER, " + - "%s DATETIME) " + - "END", - tableName, tableName, INFO_TABLE_IDENTIFIER_COLUMN, - INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, - INFO_TABLE_LAST_MODIFIED_COLUMN); */ - String sql = String.format( - "CREATE TABLE IF NOT EXISTS %s (" + - "%s VARCHAR(4096) NOT NULL, " + - "%s INTEGER, " + - "%s INTEGER, " + - "%s DATETIME);", - tableName, INFO_TABLE_IDENTIFIER_COLUMN, - INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, - INFO_TABLE_LAST_MODIFIED_COLUMN); - PreparedStatement statement = conn.prepareStatement(sql); - statement.execute(); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); - } - } else { - throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); - } - } - - public void dropTables() throws IOException { - dropImageTable(); - dropInfoTable(); - } - - private void dropImageTable() throws IOException { - Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - Connection conn = getConnection(); - String sql = "DROP TABLE " + tableName; - PreparedStatement statement = conn.prepareStatement(sql); - statement.execute(); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); - } - } else { - throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); - } - } - - private void dropInfoTable() throws IOException { - Configuration config = Application.getConfiguration(); - String tableName = config.getString(INFO_TABLE_CONFIG_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - Connection conn = getConnection(); - String sql = "DROP TABLE " + tableName; - PreparedStatement statement = conn.prepareStatement(sql); - statement.execute(); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); + if (getImageInputStream(params) == null) { + logger.debug("Miss; caching {}", params); + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + return new JdbcImageOutputStream(getConnection(), params); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); + } + } else { + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } - } else { - throw new IOException(INFO_TABLE_CONFIG_KEY + " is not set"); } + return null; } private Timestamp now() { @@ -520,6 +529,7 @@ public void putDimension(Identifier identifier, Dimension dimension) statement.setInt(3, dimension.height); statement.setTimestamp(4, now()); statement.executeUpdate(); + logger.debug("Cached dimension: {}", identifier); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } From b9c46c17dfa13fbe689845a4adc4e4d0c0b39c41 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 12:31:35 -0500 Subject: [PATCH 12/24] Remove an unnecessary check in getImageOutputStream() --- .../cantaloupe/cache/FilesystemCache.java | 13 ++++------- .../library/cantaloupe/cache/JdbcCache.java | 23 ++++++++----------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/FilesystemCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/FilesystemCache.java index 86c95356c..c9861190e 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/FilesystemCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/FilesystemCache.java @@ -277,14 +277,11 @@ public InputStream getImageInputStream(Parameters params) { @Override public OutputStream getImageOutputStream(Parameters params) throws IOException { // TODO: make this work better concurrently - if (getImageInputStream(params) == null) { - logger.debug("Miss; caching {}", params); - File cacheFile = getCachedImageFile(params); - cacheFile.getParentFile().mkdirs(); - cacheFile.createNewFile(); - return new FileOutputStream(cacheFile); - } - return null; + logger.debug("Miss; caching {}", params); + File cacheFile = getCachedImageFile(params); + cacheFile.getParentFile().mkdirs(); + cacheFile.createNewFile(); + return new FileOutputStream(cacheFile); } /** diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index 0c9062c36..f8e6c2161 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -475,21 +475,18 @@ public InputStream getImageInputStream(Parameters params) { @Override public OutputStream getImageOutputStream(Parameters params) throws IOException { - if (getImageInputStream(params) == null) { - logger.debug("Miss; caching {}", params); - Configuration config = Application.getConfiguration(); - String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); - if (tableName != null && tableName.length() > 0) { - try { - return new JdbcImageOutputStream(getConnection(), params); - } catch (SQLException e) { - throw new IOException(e.getMessage(), e); - } - } else { - throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); + logger.debug("Miss; caching {}", params); + Configuration config = Application.getConfiguration(); + String tableName = config.getString(IMAGE_TABLE_CONFIG_KEY, ""); + if (tableName != null && tableName.length() > 0) { + try { + return new JdbcImageOutputStream(getConnection(), params); + } catch (SQLException e) { + throw new IOException(e.getMessage(), e); } + } else { + throw new IOException(IMAGE_TABLE_CONFIG_KEY + " is not set"); } - return null; } private Timestamp now() { From 349db991be9bab884ff33344b919d09f01967be1 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 14:16:39 -0500 Subject: [PATCH 13/24] WIP --- .../library/cantaloupe/cache/JdbcCache.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index f8e6c2161..17581e568 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -46,12 +46,7 @@ public JdbcImageOutputStream(Connection conn, Parameters params) { @Override public void close() throws IOException { - outputStream.close(); - } - - @Override - public void flush() throws IOException { - outputStream.flush(); + System.out.println("closing"); try { Configuration config = Application.getConfiguration(); String sql = String.format( @@ -67,9 +62,16 @@ public void flush() throws IOException { statement.executeUpdate(); } catch (SQLException e) { throw new IOException(e.getMessage(), e); + } finally { + outputStream.close(); } } + @Override + public void flush() throws IOException { + outputStream.flush(); + } + @Override public void write(int b) throws IOException { outputStream.write(b); From 51c8e2ec098d9e706556ce366869ac4e6b008a0a Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 21:08:07 -0500 Subject: [PATCH 14/24] JdbcCache works --- .../library/cantaloupe/cache/Cache.java | 14 ++- .../library/cantaloupe/cache/JdbcCache.java | 43 +++++---- .../cantaloupe/resource/ImageResource.java | 94 ++++++++++--------- .../cantaloupe/cache/JdbcCacheTest.java | 10 +- 4 files changed, 92 insertions(+), 69 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java index c75e82c72..6f71fc72c 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/Cache.java @@ -10,12 +10,12 @@ /** * Interface to be implemented by all caches. Instances will be shared - * Singletons. + * Singletons, so must be thread-safe. */ public interface Cache { /** - * Deletes the entire cache contents. Must be thread-safe. + * Deletes the entire cache contents. * * @throws IOException */ @@ -38,6 +38,11 @@ public interface Cache { void flushExpired() throws IOException; /** + *

        Returns an InputStream corresponding to the given parameters.

        + * + *

        If an image corresponding to the given parameters exists in the + * cache but is expired, implementations should delete it.

        + * * @param params IIIF request parameters * @return An input stream corresponding to the given parameters, or null * if a non-expired image corresponding to the given parameters does not @@ -46,7 +51,10 @@ public interface Cache { InputStream getImageInputStream(Parameters params); /** - * Reads cached dimension information. + *

        Reads cached dimension information.

        + * + *

        If a dimension corresponding to the given parameters exists in the + * cache but is expired, implementations should delete it.

        * * @param identifier IIIF identifier * @return Dimension corresponding to the given identifier, or null if no diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index 17581e568..2376c0aa6 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -46,7 +46,6 @@ public JdbcImageOutputStream(Connection conn, Parameters params) { @Override public void close() throws IOException { - System.out.println("closing"); try { Configuration config = Application.getConfiguration(); String sql = String.format( @@ -418,21 +417,23 @@ public Dimension getDimension(Identifier identifier) throws IOException { if (tableName != null && tableName.length() > 0) { try { String sql = String.format( - "SELECT %s, %s FROM %s WHERE %s = ? AND %s > ?", + "SELECT %s, %s, %s FROM %s WHERE %s = ?", INFO_TABLE_WIDTH_COLUMN, INFO_TABLE_HEIGHT_COLUMN, - tableName, INFO_TABLE_IDENTIFIER_COLUMN, - INFO_TABLE_LAST_MODIFIED_COLUMN); + INFO_TABLE_LAST_MODIFIED_COLUMN, tableName, + INFO_TABLE_IDENTIFIER_COLUMN); PreparedStatement statement = getConnection().prepareStatement(sql); statement.setString(1, identifier.getValue()); - statement.setTimestamp(2, oldestDate); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { - logger.debug("Hit for dimension: {}", identifier); - return new Dimension( - resultSet.getInt(INFO_TABLE_WIDTH_COLUMN), - resultSet.getInt(INFO_TABLE_HEIGHT_COLUMN)); - } else { - flushInfo(identifier); + if (resultSet.getTimestamp(3).after(oldestDate)) { + logger.debug("Hit for dimension: {}", identifier); + return new Dimension( + resultSet.getInt(INFO_TABLE_WIDTH_COLUMN), + resultSet.getInt(INFO_TABLE_HEIGHT_COLUMN)); + } else { + logger.debug("Miss for dimension: {}", identifier); + flushInfo(identifier); + } } } catch (SQLException e) { throw new IOException(e.getMessage(), e); @@ -453,19 +454,23 @@ public InputStream getImageInputStream(Parameters params) { try { Connection conn = getConnection(); String sql = String.format( - "SELECT %s FROM %s WHERE %s = ? AND %s > ?", - IMAGE_TABLE_IMAGE_COLUMN, tableName, - IMAGE_TABLE_PARAMS_COLUMN, - IMAGE_TABLE_LAST_MODIFIED_COLUMN); + "SELECT %s, %s FROM %s WHERE %s = ?", + IMAGE_TABLE_IMAGE_COLUMN, + IMAGE_TABLE_LAST_MODIFIED_COLUMN, tableName, + IMAGE_TABLE_PARAMS_COLUMN); PreparedStatement statement = conn.prepareStatement(sql); statement.setString(1, params.toString()); - statement.setTimestamp(2, oldestDate); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { - logger.debug("Hit for image: {}", params); - inputStream = resultSet.getBinaryStream(1); + if (resultSet.getTimestamp(2).after(oldestDate)) { + logger.debug("Hit for image: {}", params); + inputStream = resultSet.getBinaryStream(1); + } else { + logger.debug("Miss for image: {}", params); + flushImage(params); + } } - } catch (SQLException e) { + } catch (IOException | SQLException e) { logger.error(e.getMessage(), e); } } else { diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java index 68fb624be..60cb9361c 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/ImageResource.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.List; import java.util.Map; import java.util.Set; @@ -27,7 +26,6 @@ import edu.illinois.library.cantaloupe.resolver.StreamResolver; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.TeeOutputStream; -import org.restlet.data.CacheDirective; import org.restlet.data.MediaType; import org.restlet.representation.OutputRepresentation; import org.restlet.resource.Get; @@ -102,28 +100,67 @@ public ImageRepresentation(MediaType mediaType, * @param outputStream Response body output stream supplied by Restlet * @throws IOException */ + @Override public void write(OutputStream outputStream) throws IOException { Cache cache = CacheFactory.getInstance(); - if (cache != null) { - try (InputStream cacheStream = cache.getImageInputStream(this.params)) { - if (cacheStream != null) { - IOUtils.copy(cacheStream, outputStream); - } else { - TeeOutputStream tos = new TeeOutputStream(outputStream, - cache.getImageOutputStream(this.params)); - doWrite(tos, cache); + try { + if (cache != null) { + OutputStream cacheOutputStream = null; + try (InputStream cacheInputStream = + cache.getImageInputStream(this.params)) { + if (cacheInputStream != null) { + IOUtils.copy(cacheInputStream, outputStream); + } else { + cacheOutputStream = cache. + getImageOutputStream(this.params); + TeeOutputStream tos = new TeeOutputStream( + outputStream, cacheOutputStream); + doCacheAwareWrite(tos, cache); + } + } catch (Exception e) { + throw new IOException(e); + } finally { + if (cacheOutputStream != null) { + cacheOutputStream.close(); + } } - } catch (Exception e) { - throw new IOException(e); + } else { + doWrite(outputStream); } - } else { + } finally { + /* + TODO: doesn't work with Java2dProcessor.process() - try in release()? + try { + if (this.inputStream != null) { + this.inputStream.close(); + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } */ + } + } + + /** + * Variant of doWrite() that cleans up incomplete cached images when + * the connection has been interrupted. + * + * @param outputStream + * @param cache + * @throws IOException + */ + private void doCacheAwareWrite(TeeOutputStream outputStream, + Cache cache) throws IOException { + try { doWrite(outputStream); + } catch (IOException e) { + logger.error(e.getMessage(), e); + cache.flush(this.params); } } private void doWrite(OutputStream outputStream) throws IOException { try { - long msec = System.currentTimeMillis(); + final long msec = System.currentTimeMillis(); // if the parameters request an unmodified source image, it can // be streamed right through if (this.params.isRequestingUnmodifiedSource()) { @@ -144,7 +181,7 @@ private void doWrite(OutputStream outputStream) throws IOException { this.sourceFormat); fproc.process(this.params, this.sourceFormat, size, this.file, outputStream); - } else if (this.inputStream != null) { + } else { StreamProcessor sproc = (StreamProcessor) proc; sproc.process(this.params, this.sourceFormat, this.fullSize, this.inputStream, outputStream); @@ -155,33 +192,6 @@ private void doWrite(OutputStream outputStream) throws IOException { } } catch (Exception e) { throw new IOException(e); - } /*finally { - TODO: doesn't work with Java2dProcessor.process() - try { - if (this.inputStream != null) { - this.inputStream.close(); - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - }*/ - } - - /** - * Variant of doWrite() that cleans up incomplete cached images when - * the connection has been interrupted. - * - * @param outputStream - * @param cache - * @throws IOException - */ - private void doWrite(OutputStream outputStream, Cache cache) - throws IOException { - try { - doWrite(outputStream); - } catch (IOException e) { - logger.error(e.getMessage(), e); - cache.flush(this.params); } } diff --git a/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java b/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java index 3e3827ab7..dc75f9e36 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/cache/JdbcCacheTest.java @@ -42,19 +42,19 @@ public void setUp() throws Exception { "default", "jpg"); OutputStream os = instance.getImageOutputStream(params); IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); - os.flush(); + os.close(); params = new Parameters("dogs", "50,50,50,50", "pct:90", "0", "default", "jpg"); os = instance.getImageOutputStream(params); IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); - os.flush(); + os.close(); params = new Parameters("bunnies", "10,20,50,90", "40,", "15", "color", "png"); os = instance.getImageOutputStream(params); IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); - os.flush(); + os.close(); // persist some corresponding dimensions instance.putDimension(new Identifier("cats"), new Dimension(50, 40)); @@ -151,7 +151,7 @@ public void testFlushExpired() throws Exception { "default", "jpg"); OutputStream os = instance.getImageOutputStream(params); IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); - os.flush(); + os.close(); instance.putDimension(new Identifier("bees"), new Dimension(50, 40)); instance.flushExpired(); @@ -232,7 +232,7 @@ public void testGetImageInputStreamWithNonzeroTtl() throws Exception { "default", "jpg"); OutputStream os = instance.getImageOutputStream(params); IOUtils.copy(new FileInputStream(TestUtil.getFixture("jpg")), os); - os.flush(); + os.close(); instance.putDimension(new Identifier("bees"), new Dimension(50, 40)); // existing, non-expired image From 1c455dfe478c6d1486fa049bf4fe3ee0c8e03968 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 21:10:29 -0500 Subject: [PATCH 15/24] Add JdbcCache to change log --- website/changes.html | 1 + 1 file changed, 1 insertion(+) diff --git a/website/changes.html b/website/changes.html index 6dee49680..1c0ea6f73 100644 --- a/website/changes.html +++ b/website/changes.html @@ -7,6 +7,7 @@

        Change Log

        1.1

          +
        • Added JdbcCache.
        • Added a cache.client.enabled key to the config file.
        From 8019311f6fc99eed824feccd8c1fbbcffb375281 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 21:13:54 -0500 Subject: [PATCH 16/24] Use the Simple HTTP server --- pom.xml | 8 ++++++++ website/changes.html | 1 + 2 files changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 1749cc877..c3fc3f165 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,7 @@ janino ${janino.version} + org.im4java im4java @@ -113,11 +114,18 @@ org.restlet.ext.jackson ${restlet.version} + + + org.restlet.jse + org.restlet.ext.simple + ${restlet.version} + org.restlet.jse org.restlet.ext.slf4j ${restlet.version} + org.restlet.jse org.restlet.ext.velocity diff --git a/website/changes.html b/website/changes.html index 1c0ea6f73..0763dcbef 100644 --- a/website/changes.html +++ b/website/changes.html @@ -9,6 +9,7 @@

        1.1

        • Added JdbcCache.
        • Added a cache.client.enabled key to the config file.
        • +
        • Switched to the Simple HTTP server.

        1.0.1

        From f43816065c27b3811de1ea57d79f9619eb8b3c4b Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 21:14:08 -0500 Subject: [PATCH 17/24] Improve table-create log messages --- .../java/edu/illinois/library/cantaloupe/cache/JdbcCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java index 2376c0aa6..668c0111c 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java +++ b/src/main/java/edu/illinois/library/cantaloupe/cache/JdbcCache.java @@ -156,7 +156,7 @@ private static void createImageTable() throws IOException { } PreparedStatement statement = conn.prepareStatement(sql); statement.execute(); - logger.info("Created table: {}", tableName); + logger.info("Created table (if not already existing): {}", tableName); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } @@ -196,7 +196,7 @@ private static void createInfoTable() throws IOException { } PreparedStatement statement = conn.prepareStatement(sql); statement.execute(); - logger.info("Created table: {}", tableName); + logger.info("Created table (if not already existing): {}", tableName); } catch (SQLException e) { throw new IOException(e.getMessage(), e); } From 86552624f6cf20526db34f84c1080814f67dbc43 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 21:30:31 -0500 Subject: [PATCH 18/24] Simple HTTP Server uses "Content-Type" instead of "Content-type" used by Java HTTP Server --- .../library/cantaloupe/iiif/Iiif20ConformanceTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java b/src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java index 9237a67f4..1cac5e93d 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/iiif/Iiif20ConformanceTest.java @@ -489,7 +489,7 @@ private void testFormat(OutputFormat format) throws Exception { client.get(); assertEquals(Status.SUCCESS_OK, client.getStatus()); assertEquals(format.getMediaType(), - client.getResponse().getHeaders().getFirst("Content-type").getValue()); + client.getResponse().getHeaders().getFirst("Content-Type").getValue()); } else { try { client.get(); @@ -551,7 +551,7 @@ public void testInformationRequestContentType() throws IOException { ClientResource client = getClientForUriPath("/" + IMAGE + "/info.json"); client.get(); assertEquals("application/json; charset=UTF-8", - client.getResponse().getHeaders().getFirst("Content-type").getValue()); + client.getResponse().getHeaders().getFirst("Content-Type").getValue()); } /** @@ -566,13 +566,13 @@ public void testInformationRequestContentTypeJsonLd() throws IOException { client.accept(new MediaType("application/ld+json")); client.get(); assertEquals("application/ld+json; charset=UTF-8", - client.getResponse().getHeaders().getFirst("Content-type").getValue()); + client.getResponse().getHeaders().getFirst("Content-Type").getValue()); client = getClientForUriPath("/" + IMAGE + "/info.json"); client.accept(new MediaType("application/json")); client.get(); assertEquals("application/json; charset=UTF-8", - client.getResponse().getHeaders().getFirst("Content-type").getValue()); + client.getResponse().getHeaders().getFirst("Content-Type").getValue()); } /** From 1843b07cb17c57c741c9d6d42f29c8642b5d5d1f Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Tue, 27 Oct 2015 21:31:22 -0500 Subject: [PATCH 19/24] Use TestUtil.getFixture() --- .../processor/KakaduProcessorTest.java | 5 +- .../cantaloupe/processor/ProcessorTest.java | 60 ++++++++----------- .../cantaloupe/resolver/JdbcResolverTest.java | 14 +---- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/test/java/edu/illinois/library/cantaloupe/processor/KakaduProcessorTest.java b/src/test/java/edu/illinois/library/cantaloupe/processor/KakaduProcessorTest.java index d0a7ca688..dc70630ce 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/processor/KakaduProcessorTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/processor/KakaduProcessorTest.java @@ -4,6 +4,7 @@ import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.OutputFormat; import edu.illinois.library.cantaloupe.request.Quality; +import edu.illinois.library.cantaloupe.test.TestUtil; import java.awt.Dimension; import java.io.FileInputStream; @@ -38,13 +39,13 @@ public void testGetSize() throws Exception { if (getProcessor() instanceof StreamProcessor) { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension actualSize = proc.getSize( - new FileInputStream(getFixture("jp2")), + new FileInputStream(TestUtil.getFixture("jp2")), SourceFormat.JP2); assertEquals(expectedSize, actualSize); } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - Dimension actualSize = proc.getSize(getFixture("jp2"), + Dimension actualSize = proc.getSize(TestUtil.getFixture("jp2"), SourceFormat.JP2); assertEquals(expectedSize, actualSize); } diff --git a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java index 7ee97ae6f..870e11e72 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/processor/ProcessorTest.java @@ -5,16 +5,14 @@ import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.OutputFormat; import edu.illinois.library.cantaloupe.request.Parameters; +import edu.illinois.library.cantaloupe.test.TestUtil; import org.apache.commons.configuration.BaseConfiguration; import java.awt.Dimension; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Set; /** @@ -35,13 +33,6 @@ protected SourceFormat getAnySupportedSourceFormat(Processor processor) { return null; } - protected File getFixture(String filename) throws IOException { - File directory = new File("."); - String cwd = directory.getCanonicalPath(); - Path testPath = Paths.get(cwd, "src", "test", "resources"); - return new File(testPath + File.separator + filename); - } - protected abstract Processor getProcessor(); public void testGetSize() throws Exception { @@ -49,15 +40,15 @@ public void testGetSize() throws Exception { if (getProcessor() instanceof StreamProcessor) { StreamProcessor proc = (StreamProcessor) getProcessor(); try (InputStream inputStream = new FileInputStream( - getFixture("escher_lego.jpg"))) { + TestUtil.getFixture("escher_lego.jpg"))) { Dimension actualSize = proc.getSize(inputStream, SourceFormat.JPG); assertEquals(expectedSize, actualSize); } } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - Dimension actualSize = proc.getSize(getFixture("escher_lego.jpg"), - SourceFormat.JPG); + Dimension actualSize = proc.getSize( + TestUtil.getFixture("escher_lego.jpg"), SourceFormat.JPG); assertEquals(expectedSize, actualSize); } } @@ -69,9 +60,9 @@ public void testProcessWithSupportedSourceFormatsAndNoTransformation() throws Ex if (getProcessor().getAvailableOutputFormats(sourceFormat).size() > 0) { if (getProcessor() instanceof StreamProcessor) { InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension size = proc.getSize(sizeInputStream, sourceFormat); @@ -86,7 +77,7 @@ public void testProcessWithSupportedSourceFormatsAndNoTransformation() throws Ex } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture(sourceFormat.getPreferredExtension()); Dimension size = proc.getSize(file, sourceFormat); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proc.process(params, sourceFormat, size, file, outputStream); @@ -103,9 +94,9 @@ public void testProcessWithUnsupportedSourceFormats() throws Exception { if (getProcessor().getAvailableOutputFormats(sourceFormat).size() == 0) { if (getProcessor() instanceof StreamProcessor) { InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension size = proc.getSize(sizeInputStream, sourceFormat); @@ -124,7 +115,8 @@ public void testProcessWithUnsupportedSourceFormats() throws Exception { if (getProcessor() instanceof FileProcessor) { try { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture( + sourceFormat.getPreferredExtension()); Dimension size = proc.getSize(file, sourceFormat); proc.process(params, sourceFormat, size, file, new NullOutputStream()); @@ -148,9 +140,9 @@ public void testProcessWithRegionTransformation() throws Exception { if (getProcessor().getAvailableOutputFormats(sourceFormat).size() > 0) { if (getProcessor() instanceof StreamProcessor) { InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension size = proc.getSize(sizeInputStream, @@ -166,7 +158,7 @@ public void testProcessWithRegionTransformation() throws Exception { } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture(sourceFormat.getPreferredExtension()); Dimension size = proc.getSize(file, sourceFormat); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proc.process(params, sourceFormat, size, file, outputStream); @@ -186,9 +178,9 @@ public void testProcessWithSizeTransformation() throws Exception { if (getProcessor().getAvailableOutputFormats(sourceFormat).size() > 0) { if (getProcessor() instanceof StreamProcessor) { InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension fullSize = proc.getSize(sizeInputStream, @@ -204,7 +196,7 @@ public void testProcessWithSizeTransformation() throws Exception { } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture(sourceFormat.getPreferredExtension()); Dimension fullSize = proc.getSize(file, sourceFormat); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proc.process(params, sourceFormat, fullSize, file, outputStream); @@ -224,9 +216,9 @@ public void testProcessWithRotationTransformation() throws Exception { if (getProcessor().getAvailableOutputFormats(sourceFormat).size() > 0) { if (getProcessor() instanceof StreamProcessor) { InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension size = proc.getSize(sizeInputStream, @@ -242,7 +234,7 @@ public void testProcessWithRotationTransformation() throws Exception { } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture(sourceFormat.getPreferredExtension()); Dimension size = proc.getSize(file, sourceFormat); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proc.process(params, sourceFormat, size, file, outputStream); @@ -262,9 +254,9 @@ public void testProcessWithQualityTransformation() throws Exception { if (getProcessor().getAvailableOutputFormats(sourceFormat).size() > 0) { if (getProcessor() instanceof StreamProcessor) { InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension size = proc.getSize(sizeInputStream, @@ -280,7 +272,7 @@ public void testProcessWithQualityTransformation() throws Exception { } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture(sourceFormat.getPreferredExtension()); Dimension size = proc.getSize(file, sourceFormat); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proc.process(params, sourceFormat, size, file, outputStream); @@ -301,9 +293,9 @@ public void testProcessWithSupportedOutputFormats() throws Exception { if (getProcessor().getAvailableOutputFormats(sourceFormat).size() > 0) { if (getProcessor() instanceof StreamProcessor) { InputStream sizeInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); InputStream processInputStream = new FileInputStream( - getFixture(sourceFormat.getPreferredExtension())); + TestUtil.getFixture(sourceFormat.getPreferredExtension())); try { StreamProcessor proc = (StreamProcessor) getProcessor(); Dimension size = proc.getSize(sizeInputStream, @@ -320,7 +312,7 @@ public void testProcessWithSupportedOutputFormats() throws Exception { } if (getProcessor() instanceof FileProcessor) { FileProcessor proc = (FileProcessor) getProcessor(); - File file = getFixture(sourceFormat.getPreferredExtension()); + File file = TestUtil.getFixture(sourceFormat.getPreferredExtension()); Dimension size = proc.getSize(file, sourceFormat); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); proc.process(params, sourceFormat, size, file, diff --git a/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java b/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java index 4f2efbc1e..e8581d9bd 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resolver/JdbcResolverTest.java @@ -4,14 +4,12 @@ import edu.illinois.library.cantaloupe.CantaloupeTestCase; import edu.illinois.library.cantaloupe.image.SourceFormat; import edu.illinois.library.cantaloupe.request.Identifier; +import edu.illinois.library.cantaloupe.test.TestUtil; import org.apache.commons.configuration.BaseConfiguration; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; import java.sql.Connection; import java.sql.PreparedStatement; @@ -47,7 +45,8 @@ public void setUp() throws Exception { statement = conn.prepareStatement(sql); statement.setString(1, "jpg.jpg"); statement.setString(2, "image/jpeg"); - statement.setBinaryStream(3, new FileInputStream(getFixture("jpg"))); + statement.setBinaryStream(3, + new FileInputStream(TestUtil.getFixture("jpg"))); statement.executeUpdate(); instance = new JdbcResolver(); @@ -114,11 +113,4 @@ public void testExecuteGetMediaType() throws Exception { assertEquals("SELECT media_type FROM items WHERE filename = ?", result); } - protected File getFixture(String filename) throws IOException { - File directory = new File("."); - String cwd = directory.getCanonicalPath(); - Path testPath = Paths.get(cwd, "src", "test", "resources"); - return new File(testPath + File.separator + filename); - } - } From 4f3b8f39a4b634a4654e30efaaf804505fcae277 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Fri, 6 Nov 2015 18:18:45 -0600 Subject: [PATCH 20/24] CustomStatusService.toStatus() prefers reporting the throwable's cause --- .../cantaloupe/ImageServerApplication.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java b/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java index 8e0223863..8c656dc99 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java +++ b/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java @@ -18,6 +18,7 @@ import org.restlet.ext.velocity.TemplateRepresentation; import org.restlet.representation.Representation; import org.restlet.resource.Directory; +import org.restlet.resource.ResourceException; import org.restlet.routing.Redirector; import org.restlet.routing.Router; import org.restlet.routing.Template; @@ -106,16 +107,19 @@ public Status getStatus(Throwable t, Request request, public Status toStatus(Throwable t, Request request, Response response) { Status status; - Throwable cause = t.getCause(); - if (cause instanceof IllegalArgumentException || - cause instanceof UnsupportedEncodingException || - cause instanceof UnsupportedOutputFormatException) { + t = (t.getCause() != null) ? t.getCause() : t; + + if (t instanceof ResourceException) { + status = ((ResourceException) t).getStatus(); + } else if (t instanceof IllegalArgumentException || + t instanceof UnsupportedEncodingException || + t instanceof UnsupportedOutputFormatException) { status = new Status(Status.CLIENT_ERROR_BAD_REQUEST, t); - } else if (cause instanceof UnsupportedSourceFormatException) { + } else if (t instanceof UnsupportedSourceFormatException) { status = new Status(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, t); - } else if (cause instanceof FileNotFoundException) { + } else if (t instanceof FileNotFoundException) { status = new Status(Status.CLIENT_ERROR_NOT_FOUND, t); - } else if (cause instanceof AccessDeniedException) { + } else if (t instanceof AccessDeniedException) { status = new Status(Status.CLIENT_ERROR_FORBIDDEN, t); } else { status = new Status(Status.SERVER_ERROR_INTERNAL, t); From c6c0a3b42861edfb8c73a4f2935b3479f33f49ad Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Fri, 6 Nov 2015 18:21:09 -0600 Subject: [PATCH 21/24] Restlet docs say nothing about createInboundRoot() needing to be synchronized --- .../edu/illinois/library/cantaloupe/ImageServerApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java b/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java index 8c656dc99..9801c1c83 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java +++ b/src/main/java/edu/illinois/library/cantaloupe/ImageServerApplication.java @@ -140,7 +140,7 @@ public ImageServerApplication() { * @see URI Syntax */ @Override - public synchronized Restlet createInboundRoot() { + public Restlet createInboundRoot() { final Router router = new Router(getContext()); router.setDefaultMatchingMode(Template.MODE_EQUALS); From b4c1877866808003c54ef48721ac777c60afd04d Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Fri, 6 Nov 2015 18:54:04 -0600 Subject: [PATCH 22/24] Correct the jekyll website URL --- website/developers.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/website/developers.html b/website/developers.html index 08effcc39..64037b34f 100644 --- a/website/developers.html +++ b/website/developers.html @@ -53,19 +53,17 @@

        Website

        The Jekyll website compiler transforms the HTML pages in the website folder into a working static website. The build/deploy_website.rb script takes care of building the website, adding the current Javadoc to it, committing it, and deploying it to GitHub Pages.

        -

        To merely preview the website without releasing it, cd into the website folder and run jekyll serve, then go to http://localhost:4000/ in a web browser.

        +

        To merely preview the website without releasing it, cd into the website folder and run jekyll serve, then go to http://localhost:4000/cantaloupe/ in a web browser.

        Versioning

        -

        Cantaloupe roughly uses semantic versioning. Major releases (n) break backwards compatibility in a major way. Minor releases (n.n) either do not break it, or only in a minor/trivial way. Patch releases (n.n.n) are for bugfixes only.

        +

        Cantaloupe roughly uses semantic versioning. Major releases (n) break backwards compatibility in a significant way. Minor releases (n.n) either do not break compatibility, or only in a trivial way. Patch releases (n.n.n) are for bugfixes only.

        -

        Note that the above statement covers public API (the HTTP endpoints) only. Internals may change significantly from release to release (though this is not really likely.)

        - -

        Because Cantaloupe is generally intended to provide stable APIs, major version increments should be expected to be rare.

        +

        Note that the above statement applies only to the HTTP endpoints. Internal public API may change significantly from release to release (though this is not really likely.)

        Releasing

        -

        Currently, the release process is mostly manual. The steps involved are:

        +

        Currently, the release process is mostly manual, consisting of the following steps:

        1. Finalize the code to be released, addressing any relevant milestone issues, TODOs, etc.
        2. From 892eebaeb675c17243af3d4c3fae6665ba839f5b Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Fri, 6 Nov 2015 18:55:48 -0600 Subject: [PATCH 23/24] Revert version to SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c3fc3f165..049599bd9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,14 @@ edu.illinois.library.cantaloupe Cantaloupe jar - 1.0.1 + SNAPSHOT Cantaloupe https://medusa-project.github.io/cantaloupe/ UTF-8 UTF-8 - 1.0.1 + SNAPSHOT 2.4 3.4 1.4.190 From b0c2a740bff623f6fcd25e075aa7699216c12ed4 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Fri, 6 Nov 2015 18:59:38 -0600 Subject: [PATCH 24/24] Set version to 1.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 049599bd9..5f47a9cc5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,14 @@ edu.illinois.library.cantaloupe Cantaloupe jar - SNAPSHOT + 1.1 Cantaloupe https://medusa-project.github.io/cantaloupe/ UTF-8 UTF-8 - SNAPSHOT + 1.1 2.4 3.4 1.4.190