Skip to content

Commit

Permalink
done mostly w/ java proxy example
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub Holy committed Apr 8, 2011
1 parent 98ef073 commit c32264c
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 26 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -1 +1,3 @@
target
testData/testDB
derby.log
14 changes: 14 additions & 0 deletions pom.xml
Expand Up @@ -26,6 +26,20 @@
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.jakubholy.testing</groupId>
<artifactId>dbunit-embeddedderby-parenttest</artifactId>
<version>1.2.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.5.6</version>
<type>jar</type>
<scope>runtime</scope>
</dependency>
</dependencies>

<profiles>
Expand Down
Expand Up @@ -37,7 +37,7 @@ public Object interceptAndLog(ProceedingJoinPoint invocation) throws Throwable {
try {
return invocation.proceed();
} catch (Exception e) {
Logger.getLogger("aop").warning("LoggingAspect informs you that the method " +
Logger.getLogger("AspectJ").warning("LoggingAspect informs you that the method " +
invocation.getSignature().getName() + " failed for the input '" +
invocation.getArgs()[0] + "'. Original exception: " + e);
return null; // normally we would just rethrow it...
Expand Down
Expand Up @@ -4,26 +4,26 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.BatchUpdateException;
import java.sql.PreparedStatement;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

/**
* Remember values passed into a sql statement via setString etc. for later logging.
*
* Source: http://theholyjava.wordpress.com/2009/05/23/a-logging-wrapper-around-prepareds/
* (slightly adjusted)
*/
class LoggingStatementDecorator implements InvocationHandler {

/** File's Subversion info (version etc.). */
public static final String SVN_ID = "$id$";

private List<List<Object>> batch = new LinkedList<List<Object>>();
private List<Object> currentRow = new LinkedList<Object>();
private PreparedStatement target;
private boolean failed = false;
private int successfulBatchCounter = 0;

public LoggingStatementDecorator(PreparedStatement target) {
private LoggingStatementDecorator(PreparedStatement target) {
if (target == null) throw new IllegalArgumentException("'target' can't be null.");
this.target = target;
}
Expand All @@ -32,33 +32,46 @@ public LoggingStatementDecorator(PreparedStatement target) {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {

final Object result;

try {
result = method.invoke(target, args);
failed = false;
Object result = method.invoke(target, args);
updateLog(method, args);
return result;
} catch (InvocationTargetException e) {
failed = true;
throw e.getTargetException();
} catch (Exception e) {
failed = true;
throw e;
Throwable cause = e.getTargetException();
if (cause instanceof BatchUpdateException) {
int failedBatchNr = successfulBatchCounter + 1;
Logger.getLogger("JavaProxy").warning(
"Batch update failed for batch# " + failedBatchNr +
" (counting from 1) with values: [" +
getValuesAsCsv() + "]. Cause: " + cause.getMessage());
return null;
}
throw cause;
}

if ( method.getName().startsWith("setNull")
}

private void updateLog(Method method, Object[] args) {
if ( method.getName().startsWith("setNull")
&& (args.length >=1 && Integer.TYPE == method.getParameterTypes()[0] ) ) {
handleSetSomething((Integer) args[0], null);
} else if ( method.getName().startsWith("set")
&& (args.length >=2 && Integer.TYPE == method.getParameterTypes()[0] ) ) {
handleSetSomething((Integer) args[0], args[1]);
} else if ("addBatch".equals(method.getName())) {
handleAddBatch();
} else if ("executeBatch".equals(method.getName())) {
handleExecuteBatch();
}
}

return result;
}
private void handleExecuteBatch() {
++successfulBatchCounter;
batch.clear();

}

private void handleSetSomething(int index, Object value) {
private void handleSetSomething(int index, Object value) {
currentRow.add(value);
}

Expand All @@ -73,11 +86,6 @@ public List<List<Object>> getValues() {

public PreparedStatement getTarget() { return target; }

/** Has the last method called on the Statement caused an exception? */
public boolean isFailed() { return failed; }

public String toString() { return "LoggingHandler[failed="+failed+"]"; }

/** Values as comma-separated values. */
public String getValuesAsCsv() {
StringBuilder csv = new StringBuilder();
Expand All @@ -94,11 +102,11 @@ public String getValuesAsCsv() {
return csv.toString();
} /* getValuesAsCsv */

public PreparedStatement createProxy() {
public static PreparedStatement createProxy(PreparedStatement target) {
return (PreparedStatement) Proxy.newProxyInstance(
PreparedStatement.class.getClassLoader(),
new Class[] { PreparedStatement.class },
this);
new LoggingStatementDecorator(target));
};

}
73 changes: 73 additions & 0 deletions src/main/java/iterate/jz2011/codeinjection/javaproxy/Main.java
@@ -0,0 +1,73 @@
package iterate.jz2011.codeinjection.javaproxy;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import net.jakubholy.testing.dbunit.embeddeddb.EmbeddedDbTester;

public class Main {

private static final int BATCH_SIZE = 2;

private static final String ILLEGAL_VALUE = "Only values of 10 or less characters are allowed";

private static EmbeddedDbTester testDb = new EmbeddedDbTester();

private void failingJdbcBatchInsert(Connection connection, Map<Integer, String> data) throws SQLException {

PreparedStatement rawPrepStmt = connection.prepareStatement("INSERT INTO my_test_schema.my_test_table (id,some_text) VALUES (?,?)");
PreparedStatement loggingPrepStmt = LoggingStatementDecorator.createProxy(rawPrepStmt);

int batchCounter = 0;
for (Entry<Integer, String> row : data.entrySet()) {
loggingPrepStmt.setInt(1, row.getKey());
loggingPrepStmt.setString(2, row.getValue());
loggingPrepStmt.addBatch();
++batchCounter;

if (batchCounter % BATCH_SIZE == 0)
loggingPrepStmt.executeBatch();
}

// Execute remaining batches if any
if (batchCounter % BATCH_SIZE != 0)
loggingPrepStmt.executeBatch();

}

/**
* Initialize DB, perform updates.
*/
public static void main(String[] args) throws Exception {

// Create the DB from the DDL
try {
net.jakubholy.testing.dbunit.embeddeddb.DatabaseCreator.createAndInitializeTestDb();
} catch (BatchUpdateException e) {/* perhaps the DB exists already*/}

// Initialize DB connection, clear existing data from a previous run
testDb.onSetup();

// Prepare & insert data!
@SuppressWarnings("serial")
Map<Integer, String> data = new HashMap<Integer, String>() {{
// Batch 0
put(100, "ok value 1");
put(200, "ok value 2");
// Batch 1
put(300, "ok value 3");
put(300, ILLEGAL_VALUE);
// Batch 2
put(400, "ok value 4");
}};

new Main().failingJdbcBatchInsert(testDb.getSqlConnection(), data);

}

}
10 changes: 10 additions & 0 deletions testData/create_db_content.ddl
@@ -0,0 +1,10 @@
-- DDL file defining the schemas and tables to create in the test database.
-- Read and executed by net.jakubholy.testing.dbunit.DatabaseCreator#createDbSchemaFromDdl(Connection)
-- see net.jakubholy.testing.dbunit.DatabaseCreator#main.

-- Replace the text below with whatever you need.
create schema my_test_schema;

create table my_test_schema.my_test_table (
id int primary key
, some_text varchar(10));
47 changes: 47 additions & 0 deletions testData/dbunit-test_data_set.xml
@@ -0,0 +1,47 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--DOCTYPE dataset SYSTEM 'testData/dbunit-test_data_set.dtd'-->
<!--
See DbUnit's XmlDataSet.
Database definition for AbstractEmbeddedDbTestCase
- uses DbUnit to load the data into an embedded derby DB -
To connect to the DB: user sa, psw emty, url
jdbc:derby:<absolute path to this project>\testData\testDB
Note: Only 1 connection to the Derby DB at a time is allowed!!!!
IMPORTANT
=========
1) You must have <table> entry for any table you want DbUnit to clear for you.
Even if you don't want DbUnit to insert any data you must still declare at least
<table name="yourSchema.yourTable" /> for the table yourSchema.yourTable to be emptied during test setUp().
2) When using a column that contains XML value, you may use <![CDATA[ to supply the value.
But there should be no space within '<value><![CDATA[<?xml ...' otherwise you may get Xerces'
exception 'The processing instruction target matching "[xX][mM][lL]" is not allowed.'
ABOUT FILE FORMAT
=================
This format is better than flat data set because it describes explicitly the
columns though it's also more verbose.
Also, thanks to setting the DbUnit feature QUALIFIED_TABLE_NAMES to true in the test,
we can use fully qualified table names and thus have tables in different schemas.
HOW TO CREATE
=============
To create a DbUnit data set like this you've several options:
1. Write it manually.
2. Use DbUnit itself to connect to a DB, load a data set and store it into a XmlDataSet.
3. Use the Eclipse plugin QuantumDB and its ability to export table into DbUnit data set format.
4. Use Jailer (http://jailer.sourceforge.net/home.htm) - it can export also only a sample of
a DB while preserving referential integrity.
-->
<dataset>

<!--
######### TABLES TO CLEAR WITHOUT INSERTING ANY DATA DURING TestCase#setUp() #########
Provide names of the tables whose content shall be dropped prior to running a test method.
You don't need to list its columns.
-->
<table name="my_test_schema.my_test_table" />

</dataset>

0 comments on commit c32264c

Please sign in to comment.