Skip to content

Commit

Permalink
Ensure JpaContentTemplate afterset and setContent methods close resou…
Browse files Browse the repository at this point in the history
…rces

[#139961121](https://www.pivotaltracker.com/story/show/139961121)

Signed-off-by: Paul Warren <paul.warren@emc.com>
  • Loading branch information
Jeff Pak authored and Paul Warren committed Feb 18, 2017
1 parent e1f230f commit 791c521
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 22 deletions.
Expand Up @@ -26,47 +26,61 @@ public class JpaContentTemplate implements ContentOperations, InitializingBean {

private static Log logger = LogFactory.getLog(JpaContentTemplate.class);

@Autowired
@PersistenceContext
private EntityManager manager;

@Autowired
private DataSource datasource;

@Override

@Autowired
public JpaContentTemplate(DataSource datasource) {
this.datasource = datasource;
}

@Override
public void afterPropertiesSet() throws Exception {
ResultSet rs = datasource.getConnection().getMetaData().getTables(null, null, "BLOBS", new String[] {"TABLE"});
if (!rs.next()) {
logger.info("Creating JPA Content Repository");

Statement stmt = datasource.getConnection().createStatement();
String sql = "CREATE TABLE BLOBS " +
"(id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1), " +
" blob BLOB, " +
" PRIMARY KEY ( id ))";
ResultSet rs = null;
try {
rs = datasource.getConnection().getMetaData().getTables(null, null, "BLOBS", new String[] {"TABLE"});
if (!rs.next()) {
logger.info("Creating JPA Content Repository");

stmt.executeUpdate(sql);
}
Statement stmt = datasource.getConnection().createStatement();
String sql = "CREATE TABLE BLOBS " +
"(id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1), " +
" blob BLOB, " +
" PRIMARY KEY ( id ))";

stmt.executeUpdate(sql);
}
} finally {
rs.close();
}
}

@Override
public <T> void setContent(T metadata, InputStream content) {
if (BeanUtils.getFieldWithAnnotation(metadata, ContentId.class) == null) {

String sql = "INSERT INTO BLOBS VALUES(NULL, ?);";
ResultSet set = null;
int id = 0;
try {
PreparedStatement pS = datasource.getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
InputStreamEx in = new InputStreamEx(content);
pS.setBinaryStream(1, in);
pS.executeUpdate();
ResultSet set = pS.getGeneratedKeys();
set = pS.getGeneratedKeys();
set.next();
int id = set.getInt("ID");
id = set.getInt("ID");
BeanUtils.setFieldWithAnnotation(metadata, ContentId.class, id);
BeanUtils.setFieldWithAnnotation(metadata, ContentLength.class, in.getLength());
} catch (SQLException sqle) {
logger.error("Error inserting content",sqle);
}
} finally {
if (set != null) {
try {
set.close();
} catch (SQLException e) {
logger.error(String.format("Unexpected error closing result set for content id %s", id));
}
}
}
} else {
String sql = "UPDATE BLOBS SET blob=? WHERE id=" + BeanUtils.getFieldWithAnnotation(metadata, ContentId.class);
try {
Expand Down
@@ -0,0 +1,207 @@
package internal.org.springframework.content.operations;

import com.github.paulcwarren.ginkgo4j.Ginkgo4jConfiguration;
import com.github.paulcwarren.ginkgo4j.Ginkgo4jRunner;
import internal.org.springframework.content.jpa.operations.JpaContentTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.springframework.content.commons.annotations.ContentId;
import org.springframework.content.commons.annotations.ContentLength;

import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.sql.*;

import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.*;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.*;

@RunWith(Ginkgo4jRunner.class)
@Ginkgo4jConfiguration(threads=1) // required
public class JpaContentTemplateTest {

private JpaContentTemplate template;

// actors
private TestEntity entity;
private InputStream stream;

// mocks
private DataSource datasource;
private Connection connection;
private DatabaseMetaData metadata;
private ResultSet resultSet;
private Statement statement;

{
Describe("JpaContentTemplate", () -> {
Describe("#afterPropertiesSet", () -> {
BeforeEach(() -> {
datasource = mock(DataSource.class);
connection = mock(Connection.class);
metadata = mock(DatabaseMetaData.class);
resultSet = mock(ResultSet.class);
statement = mock(Statement.class);
});
JustBeforeEach(() -> {
template = new JpaContentTemplate(datasource);
template.afterPropertiesSet();
});
Context("given the BLOBS table does not already exist", () -> {
BeforeEach(() -> {
when(datasource.getConnection()).thenReturn(connection);
when(connection.getMetaData()).thenReturn(metadata);
when(metadata.getTables(anyObject(), anyObject(), anyObject(), anyObject())).thenReturn(resultSet);
when(resultSet.next()).thenReturn(false);
when(connection.createStatement()).thenReturn(statement);
});
It("should execute sql CREATE TABLE statement", () -> {
verify(statement).executeUpdate(anyObject());
});
It("should close the resultset", () -> {
verify(resultSet).close();
});
});
Context("given the BLOBS table exists", () -> {
BeforeEach(() -> {
when(datasource.getConnection()).thenReturn(connection);
when(connection.getMetaData()).thenReturn(metadata);
when(metadata.getTables(anyObject(), anyObject(), anyObject(), anyObject())).thenReturn(resultSet);
when(resultSet.next()).thenReturn(true);
});
It("should not execute sql CREATE TABLE statement", () -> {
verify(statement, never()).executeUpdate(anyObject());
});
It("should close the resultset", () -> {
verify(resultSet).close();
});
});
});
Describe("#setContent", () -> {
BeforeEach(() -> {
datasource = mock(DataSource.class);
connection = mock(Connection.class);
statement = mock(PreparedStatement.class);
resultSet = mock(ResultSet.class);
});
JustBeforeEach(() -> {
template = new JpaContentTemplate(datasource);
template.setContent(entity, stream);
});
Context("given new content", () -> {
BeforeEach(() -> {
entity = new TestEntity();
stream = new ByteArrayInputStream("hello content world!".getBytes());
});
Context("given a generated key is returned for the new content", () -> {
BeforeEach(() -> {
when(datasource.getConnection()).thenReturn(connection);
when(connection.prepareStatement(anyObject(), eq(Statement.RETURN_GENERATED_KEYS))).thenReturn((PreparedStatement) statement);
when(statement.getGeneratedKeys()).thenReturn(resultSet);
when(resultSet.next()).thenReturn(true);
when(resultSet.getInt(eq("ID"))).thenReturn(12345);
});
It("inserts the content into the BLOBS table", () -> {
verify((PreparedStatement) statement).setBinaryStream(eq(1), isA(InputStream.class));
});
It("should close the resultset", () -> {
verify(resultSet).close();
});
});
});
Context("given content already exists", () -> {
BeforeEach(() -> {
entity = new TestEntity(12345);
});
Context("given a connection", () -> {
BeforeEach(() -> {
when(datasource.getConnection()).thenReturn(connection);
when(connection.prepareStatement(anyObject())).thenReturn((PreparedStatement) statement);
});
It("should UPDATE sql", () -> {
verify(connection).prepareStatement(startsWith("UPDATE BLOBS SET blob=?"));
});
It("should update the existing content", () -> {
verify((PreparedStatement) statement).setBinaryStream(eq(1), isA(InputStream.class));
verify((PreparedStatement) statement).executeUpdate();
});
});
});
});

Describe("#unsetContent", () -> {
BeforeEach(() -> {
datasource = mock(DataSource.class);
connection = mock(Connection.class);
statement = mock(PreparedStatement.class);
});
JustBeforeEach(() -> {
template = new JpaContentTemplate(datasource);
template.unsetContent(entity);
});
Context("given content to be deleted", () -> {
BeforeEach(() -> {
entity = new TestEntity();
});
Context("given a connection", () -> {
BeforeEach(() -> {
when(datasource.getConnection()).thenReturn(connection);
when(connection.prepareStatement(anyObject())).thenReturn((PreparedStatement) statement);
});
It("deletes the content with a DELETE statement", () -> {
verify(connection).prepareStatement(startsWith("DELETE FROM BLOBS WHERE id="));
});
It("should update the content id metadata", () -> {
assertThat(entity.getContentId(), is(nullValue()));
});
It("should update the content length metadata", () -> {
assertThat(entity.getContentLen(), is(0L));
});
});
});
});
});
}

@Test
public void noop() {
}

public static class TestEntity {
@ContentId
private Integer contentId;
@ContentLength
private long contentLen;

public TestEntity() {
this.contentId = null;
}

public TestEntity(int contentId) {
this.contentId = new Integer(contentId);
}

public Integer getContentId() {
return this.contentId;
}

public void setContentId(Integer contentId) {
this.contentId = contentId;
}

public long getContentLen() {
return contentLen;
}

public void setContentLen(long contentLen) {
this.contentLen = contentLen;
}

}
}

0 comments on commit 791c521

Please sign in to comment.