PageRenderTime 3794ms CodeModel.GetById 36ms RepoModel.GetById 12ms app.codeStats 0ms

/src/main/java/com/xhochy/carameldb/DatabaseInvokeMethod.java

https://github.com/xhochy/carameldb
Java | 154 lines | 116 code | 18 blank | 20 comment | 20 complexity | 05326ce9941279e12d06f22a9bce98e2 MD5 | raw file
  1. package com.xhochy.carameldb;
  2. import java.io.FileNotFoundException;
  3. import java.io.InputStream;
  4. import java.sql.Connection;
  5. import java.sql.DriverManager;
  6. import java.sql.PreparedStatement;
  7. import java.sql.SQLException;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.Map.Entry;
  11. import org.apache.derby.impl.io.VFMemoryStorageFactory;
  12. import org.junit.internal.runners.statements.InvokeMethod;
  13. import org.junit.runners.model.FrameworkMethod;
  14. import org.yaml.snakeyaml.Yaml;
  15. import com.google.inject.Guice;
  16. /**
  17. * Invoker that resets the database before each test.
  18. */
  19. class DatabaseInvokeMethod extends InvokeMethod {
  20. private Object target;
  21. private String databaseFixtures = "/db-fixtures.yml";
  22. private Connection conn;
  23. private static final String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
  24. private static final String JDBC_PREFIX = "jdbc:derby:memory:";
  25. public DatabaseInvokeMethod(final FrameworkMethod testMethod, final Object target) {
  26. super(testMethod, target);
  27. CaramelFixture fixture = testMethod.getAnnotation(CaramelFixture.class);
  28. if (fixture != null) {
  29. databaseFixtures = fixture.value();
  30. }
  31. this.target = target;
  32. }
  33. private void setUpDatabase() throws InstantiationException, IllegalAccessException, ClassNotFoundException,
  34. SQLException, FileNotFoundException {
  35. Class.forName(DRIVER).newInstance();
  36. conn = DriverManager.getConnection(getJDBCString() + "create=true");
  37. loadFixtures();
  38. }
  39. @SuppressWarnings("unchecked")
  40. private void loadFixtures() throws FileNotFoundException, SQLException {
  41. // Load the fixtures file
  42. InputStream io = getClass().getResourceAsStream(databaseFixtures);
  43. Yaml yaml = new Yaml();
  44. // Parse the fixtures file
  45. Map<String, Object> data = (Map<String, Object>) yaml.load(io);
  46. initTables((Map<String, Map<String, String>>) data.get("tables"));
  47. initData((Map<String, List<Map<String, Object>>>) data.get("data"));
  48. }
  49. private void initData(final Map<String, List<Map<String, Object>>> data) throws SQLException {
  50. // If there is no data, we do not need to do something.
  51. if (data == null) {
  52. return;
  53. }
  54. // Insert the data table by table, row by row.
  55. for (Entry<String, List<Map<String, Object>>> outerEntry : data.entrySet()) {
  56. for (Map<String, Object> entry : outerEntry.getValue()) {
  57. // Step 1: Prepare the statement
  58. String[] keys = entry.keySet().toArray(new String[0]);
  59. StringBuffer sql = new StringBuffer("INSERT INTO ");
  60. sql.append(outerEntry.getKey()).append(" (");
  61. StringBuffer sql2 = new StringBuffer(") VALUES (");
  62. for (int i = 0; i < keys.length; i++) {
  63. if (i != 0) {
  64. sql.append(", ");
  65. sql2.append(", ");
  66. }
  67. sql2.append('?');
  68. sql.append("\"").append(keys[i]).append("\"");
  69. }
  70. sql.append(sql2).append(")");
  71. // Step 2: Fill it with values.
  72. PreparedStatement stmt = conn.prepareStatement(sql.toString());
  73. for (int i = 0; i < keys.length; i++) {
  74. if (entry.get(keys[i]) instanceof Integer) {
  75. stmt.setInt(i + 1, (Integer) entry.get(keys[i]));
  76. } else if (entry.get(keys[i]) instanceof String) {
  77. stmt.setString(i + 1, (String) entry.get(keys[i]));
  78. } else {
  79. throw new IllegalArgumentException("Do not know which data type to use");
  80. }
  81. }
  82. // Step 3: Execute it.
  83. stmt.executeUpdate();
  84. }
  85. }
  86. }
  87. private void initTables(final Map<String, Map<String, String>> data) throws SQLException {
  88. for (Entry<String, Map<String, String>> outerEntry : data.entrySet()) {
  89. StringBuffer sql = new StringBuffer("CREATE TABLE ");
  90. sql.append(outerEntry.getKey()).append(" (");
  91. for (Entry<String, String> innerEntry : outerEntry.getValue().entrySet()) {
  92. sql.append("\"").append(innerEntry.getKey()).append("\" ");
  93. // TODO Make this a separate function and add more types.
  94. if (innerEntry.getValue().equals("integer")) {
  95. sql.append("INT");
  96. } else if (innerEntry.getValue().equals("varchar")) {
  97. sql.append("VARCHAR(255)");
  98. }
  99. sql.append(", ");
  100. }
  101. // remove last comma
  102. sql.replace(sql.lastIndexOf(","), sql.length(), "").append(")");
  103. conn.prepareCall(sql.toString()).execute();
  104. }
  105. }
  106. /**
  107. * The JDBC connection string to connect to the database.
  108. * @return JDBCConnection String terminated with ';'
  109. */
  110. private String getJDBCString() {
  111. return JDBC_PREFIX + Integer.toHexString(target.hashCode()) + ";";
  112. }
  113. /**
  114. * Clean up the database sued in the unit tests.
  115. */
  116. private void tearDownDatabase() {
  117. boolean shutdown = false;
  118. try {
  119. conn.close();
  120. DriverManager.getConnection(getJDBCString() + "shutdown=true");
  121. } catch (SQLException e) {
  122. // Database shutdowns always trigger an exception to show it completed.
  123. shutdown = true;
  124. }
  125. if (shutdown) {
  126. VFMemoryStorageFactory.purgeDatabase(Integer.toHexString(target.hashCode()));
  127. }
  128. }
  129. @Override
  130. public void evaluate() throws Throwable {
  131. setUpDatabase();
  132. Guice.createInjector(new CaramelTestModule(conn, getJDBCString())).injectMembers(target);
  133. super.evaluate();
  134. tearDownDatabase();
  135. }
  136. }