// of the ID Service. As a result, there will be some naming
// inconsistencies throughout this source file.
-// @TODO: Revise exception handling to return custom Exceptions,
+// @TODO Revise exception handling to return custom Exceptions,
// perhaps mirroring the subset of HTTP status codes returned.
//
// We're currently overloading existing core and extension Java Exceptions
// in ways that are not consistent with their original semantic meaning.
-// @TODO: Retrieve IDGenerators from the database (via JDBC or
+// @TODO Get the JDBC driver classname and database URL from configuration;
+// better yet, substitute Hibernate for JDBC for accessing database-managed persistence.
+
+// @TODO Remove any hard-coded dependencies on MySQL.
+
+// @TODO Determine how to restrict access to ID-related tables by role.
+
+// @TODO Retrieve IDGenerators from the database (via JDBC or
// Hibernate) at initialization and refresh time.
-// @TODO: Handle concurrency.
+// @TODO Remove redundancy. If we're using JDBC, a great deal of JDBC code
+// is replicated in each method below.
+
+// @TODO Handle concurrency.
//
// Right now, with each new request we're simply instantiating
// a new IDPattern and returning its next ID. As a result,
//
// At that point, we'll also need to add code to handle concurrent requests.
-// @TODO: Verify access (public, protected, or private) to service methods.
+// @TODO Verify access (public, protected, or private) to service methods.
-// @TODO: As long as we're using JDBC, use PreparedStatements, not Statements.
+// @TODO As long as we're using JDBC, use PreparedStatements, not Statements,
+// throughout the code below.
-// @TODO: Re-consider beginnings of method names:
+// @TODO Re-consider beginnings of method names:
// - "store/get" versus:
// - "store/retrieve"
// - "save/read" (appears to be used by Hibernate),
final Logger logger = LoggerFactory.getLogger(IDServiceJdbcImpl.class);
- // @TODO Get the JDBC driver classname and database URL from configuration;
- // better yet, substitute Hibernate for JDBC for accessing database-managed persistence.
-
- // @TODO: Remove any hard-coded dependencies on MySQL.
-
- // @TODO: Determine how to restrict access to ID-related tables by role.
-
final String JDBC_DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
- final String DATABASE_URL = "jdbc:mysql://localhost:3306/cspace";
+ final String DATABASE_NAME = "cspace";
+ final String DATABASE_URL = "jdbc:mysql://localhost:3306/" + DATABASE_NAME;
final String DATABASE_USERNAME = "test";
final String DATABASE_PASSWORD = "test";
+ final String TABLE_NAME = "id_generator";
//////////////////////////////////////////////////////////////////////
/**
* Constructor (no-argument).
*/
public void IDServiceJdbcImpl() {
+
+ // @TODO Decide when and how to fail at startup, or else to correct
+ // failure conditions automatically, when preconditions are not met.
+
+ // init();
+ }
+
+ // @TODO init() and hasTable() are currently UNTESTED as of 2009-08-11T13:00-0700.
+
+ //////////////////////////////////////////////////////////////////////
+ /**
+ * Initializes the service.
+ *
+ * @throws IllegalStateException if one or more of the required preconditions
+ * for the service is not present, or is not in its required state.
+ */
+ public void init() throws IllegalStateException {
+
+ try {
+ boolean hasTable = hasTable(TABLE_NAME);
+ if (! hasTable) {
+ throw new IllegalStateException(
+ "Table " + "\'" + TABLE_NAME + "\'" + " could not be found in the database.");
+ }
+ } catch (IllegalStateException e) {
+ throw e;
+ }
+
}
+ //////////////////////////////////////////////////////////////////////
+ /**
+ * Identifies whether a specified table exists in the database.
+ *
+ * @param tablename The name of a database table.
+ *
+ * @return True if the specified table exists in the database;
+ * false if the specified table does not exist in the database.
+ *
+ * @throws IllegalStateException if an error occurs while checking for the
+ * existence of the specified table.
+ */
+ public boolean hasTable(String tablename) throws IllegalStateException {
+
+ logger.debug("> in hasTable");
+
+ if (tablename == null || tablename.equals("")) {
+ return false;
+ }
+
+ try {
+ Class.forName(JDBC_DRIVER_CLASSNAME).newInstance();
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(
+ "Error finding JDBC driver class '" +
+ JDBC_DRIVER_CLASSNAME +
+ "' to set up database connection.");
+ } catch (InstantiationException e) {
+ throw new IllegalStateException(
+ "Error instantiating JDBC driver class '" +
+ JDBC_DRIVER_CLASSNAME +
+ "' to set up database connection.");
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "Error accessing JDBC driver class '" +
+ JDBC_DRIVER_CLASSNAME +
+ "' to set up database connection.");
+ }
+
+ Connection conn = null;
+ try {
+
+ conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USERNAME, DATABASE_PASSWORD);
+
+ Statement stmt = conn.createStatement();
+
+ final String CATALOG_NAME = null;
+ final String SCHEMA_NAME_PATTERN = null;
+ final String[] TABLE_TYPES = null;
+ ResultSet tablesMatchingTableName =
+ conn.getMetaData().getTables(
+ CATALOG_NAME, SCHEMA_NAME_PATTERN, tablename, TABLE_TYPES);
+
+ boolean moreRows = tablesMatchingTableName.next();
+ if (! moreRows) {
+ return false;
+ } else {
+ return true;
+ }
+
+ } catch (SQLException e) {
+ throw new IllegalStateException(
+ "Error while checking for existance of tablebase table: " + e.getMessage());
+ } finally {
+ try {
+ if (conn != null) {
+ conn.close();
+ }
+ } catch(SQLException e) {
+ // Do nothing here
+ }
+ }
+
+ }
+
//////////////////////////////////////////////////////////////////////
/**
* Generates and returns a new ID associated with a specified ID generator.
* @param csid An identifier for an ID generator.
*
* @return A new ID associated with the specified ID generator.
+ *
+ * @throws IllegalArgumentException if the provided csid is null or empty,
+ * or if the specified ID generator can't be found.
+ *
+ * @throws IllegalStateException if a storage-related error occurred.
*/
public String newID(String csid) throws
IllegalArgumentException, IllegalStateException {
/**
- * IDServiceJdbcImplTest
- *
- * Unit tests for the ID Service's JDBC implementation class, IDServiceJdbcImpl.
- *
* This document is a part of the source code and related artifacts
* for CollectionSpace, an open source collections management system
* for museums and related institutions:
*
* You may obtain a copy of the ECL 2.0 License at
* https://source.collectionspace.org/collection-space/LICENSE.txt
- *
- * $LastChangedBy: aron $
- * $LastChangedRevision: 302 $
- * $LastChangedDate$
*/
package org.collectionspace.services.id.test;
import junit.framework.TestCase;
import static org.junit.Assert.*;
+import org.junit.BeforeClass;
+import org.junit.Test;
+/**
+ * IDServiceJdbcImplTest
+ *
+ * Unit tests for the ID Service's JDBC implementation class, IDServiceJdbcImpl.
+ *
+ * $LastChangedBy: aron $
+ * $LastChangedRevision: 302 $
+ * $LastChangedDate$
+ */
public class IDServiceJdbcImplTest extends TestCase {
+ // *IMPORTANT*
+ // @TODO This class is in an early state of a refactoring to
+ // reflect a change from IDPatterns to IDGenerators at the top level
+ // of the ID Service. As a result, there will be some naming
+ // inconsistencies throughout this source file.
+
String csid;
String nextId;
- String serializedPattern;
+ String serializedGenerator;
IDPattern pattern;
IDServiceJdbcImpl jdbc = new IDServiceJdbcImpl();
IDService service = jdbc;
final static String DEFAULT_CSID = "TEST-1";
+
+ @BeforeClass
+ public static void setUpOnce() {
+ // @TODO Check for service preconditions before running tests.
+ }
+ @Test
public void testPlaceholder() {
// Placeholder test to avoid "org.testng.TestNGException:
// Failure in JUnit mode ...: could not create/run JUnit test suite"
// errors until we add working tests to this class.
}
-
-/*
- public void testAddIDPattern() {
- jdbc.addIDPattern(DEFAULT_CSID, generateSpectrumEntryNumberTestPattern());
+/*
+ @Test
+ public void testAddIDGenerator() {
+ jdbc.addIDGenerator(DEFAULT_CSID, getSpectrumEntryNumberGenerator());
}
- public void testReadIDPattern() {
+ @Test
+ public void testReadIDGenerator() {
- serializedPattern = jdbc.getIDPattern(DEFAULT_CSID);
- pattern = IDPatternSerializer.deserialize(serializedPattern);
+ serializedGenerator = jdbc.getIDGenerator(DEFAULT_CSID);
+ pattern = IDPatternSerializer.deserialize(serializedGenerator);
assertEquals(DEFAULT_CSID, pattern.getCsid());
}
- public void testUpdateIDPattern() {
+ @Test
+ public void testUpdateIDGenerator() {
final String NEW_DESCRIPTION = "new description";
- serializedPattern = jdbc.getIDPattern(DEFAULT_CSID);
-
- pattern = IDPatternSerializer.deserialize(serializedPattern);
+ // Retrieve an existing generator, deserialize it,
+ // update its contents, serialize it, and write it back.
+ serializedGenerator = jdbc.getIDGenerator(DEFAULT_CSID);
+ pattern = IDPatternSerializer.deserialize(serializedGenerator);
pattern.setDescription(NEW_DESCRIPTION);
- serializedPattern = IDPatternSerializer.serialize(pattern);
+ serializedGenerator = IDPatternSerializer.serialize(pattern);
- jdbc.updateIDPattern(DEFAULT_CSID, serializedPattern);
+ jdbc.updateIDGenerator(DEFAULT_CSID, serializedGenerator);
- serializedPattern = jdbc.getIDPattern(DEFAULT_CSID);
- pattern = IDPatternSerializer.deserialize(serializedPattern);
+ serializedGenerator = jdbc.getIDGenerator(DEFAULT_CSID);
+ pattern = IDPatternSerializer.deserialize(serializedGenerator);
assertEquals(NEW_DESCRIPTION, pattern.getDescription());
}
- public void testDeleteIDPattern() {
- jdbc.deleteIDPattern(DEFAULT_CSID);
+ @Test
+ public void testDeleteIDGenerator() {
+ jdbc.deleteIDGenerator(DEFAULT_CSID);
}
- public void testNextIDValidPattern() {
+ @Test
+ public void testNewIDValidPattern() {
csid = DEFAULT_CSID;
try {
- jdbc.deleteIDPattern(csid);
+ jdbc.deleteIDGenerator(csid);
} catch (Exception e) {
// do nothing
}
- jdbc.addIDPattern(csid, generateSpectrumEntryNumberTestPattern());
+ jdbc.addIDGenerator(csid, getSpectrumEntryNumberGenerator());
- assertEquals("E1", service.nextID("TEST-1"));
- assertEquals("E2", service.nextID("TEST-1"));
- assertEquals("E3", service.nextID("TEST-1"));
+ assertEquals("E1", service.newID("TEST-1"));
+ assertEquals("E2", service.newID("TEST-1"));
+ assertEquals("E3", service.newID("TEST-1"));
try {
- jdbc.deleteIDPattern(csid);
+ jdbc.deleteIDGenerator(csid);
} catch (Exception e) {
// do nothing
}
- jdbc.addIDPattern(csid, generateChinAccessionNumberTestPattern());
+ jdbc.addIDGenerator(csid, getChinAccessionNumberGenerator());
String currentYear = YearIDGenerator.getCurrentYear();
- assertEquals(currentYear + ".1.1", service.nextID("TEST-1"));
- assertEquals(currentYear + ".1.2", service.nextID("TEST-1"));
- assertEquals(currentYear + ".1.3", service.nextID("TEST-1"));
+ assertEquals(currentYear + ".1.1", service.newID("TEST-1"));
+ assertEquals(currentYear + ".1.2", service.newID("TEST-1"));
+ assertEquals(currentYear + ".1.3", service.newID("TEST-1"));
try {
- jdbc.deleteIDPattern(csid);
+ jdbc.deleteIDGenerator(csid);
} catch (Exception e) {
// do nothing
}
// 1. The ID Service is running and accessible to this test; and
// 2. There is no ID pattern retrievable through that service
// with the identifier 'non-existent identifier'.
+ @Test
public void testNextIDInvalidPattern() {
try {
- nextId = service.nextID("non-existent identifier");
+ nextId = service.newID("non-existent identifier");
fail("Should have thrown IllegalArgumentException here");
} catch (IllegalArgumentException expected) {
// This Exception should be thrown, and thus the test should pass.
}
}
-
-*/
// ---------------------------------------------------------------
// Utility methods used by tests above
// ---------------------------------------------------------------
// @TODO Read test patterns from external configuration.
- public String generateSpectrumEntryNumberTestPattern() {
+
+ public String getSpectrumEntryNumberGenerator() {
pattern = new IDPattern(DEFAULT_CSID);
pattern.setDescription("SPECTRUM entry number pattern");
}
- // @TODO Read test patterns from external configuration.
- public String generateChinAccessionNumberTestPattern() {
+ public String getChinAccessionNumberGenerator() {
pattern = new IDPattern(DEFAULT_CSID);
pattern.setDescription("CHIN accession number pattern, for items without parts");
return IDPatternSerializer.serialize(pattern);
}
-
+*/
+
}