corrections, and added jdbc testing datasource from pushe

pull/59/head
Michael Joseph Walsh 2012-01-26 14:43:29 -05:00
parent 4a2015201b
commit b9dc024086
15 changed files with 462 additions and 21 deletions

View File

@ -0,0 +1,282 @@
package org.mitre.jdbc.datasource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mitre.jdbc.datasource.util.SqlFileParser;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
/**
* {@inheritDoc}
*
* @author Matt Franklin
* <p/>
* Creates a JDBC DataSource that is fully initialized with the schema and data referenced in the
* application context.
* <p/>
* <p/>
* Usage:
* <code>
* <bean id="dataSource" class="org.mitre.jdbc.datasource.H2DataSourceFactory">
* <property name="databaseName" value="mymii"/>
* <property name="persist" value="true" />
* <property name="executeScriptQuery" value="SELECT * FROM gadgets" />
* <property name="scriptLocations" >
* <list>
* <value>file:db/sequences/create_all_seq.sql</value>
* <value>file:db/tables/create_all_tables.sql</value>
* <value>classpath:test-data.sql</value>
* </list>
* </property>
* </bean>
* </code>
* <p/>
*/
public class H2DataSourceFactory implements FactoryBean {
private static Log logger = LogFactory.getLog(H2DataSourceFactory.class);
protected String databaseName;
protected Boolean persist;
protected String executeScriptQuery;
protected List<Resource> scriptLocations;
protected Map<String, String> dateConversionPatterns;
/**
* The DataSource singleton returned by the factory.
*/
protected DataSource dataSource;
/**
* Creates a new factory with no initial values for required properties.
* <p/>
* NOTE: If the factory is initialized using the default constructor, the required properties must be set prior to use
*/
public H2DataSourceFactory() {
}
/**
* Creates a new factory and sets teh properties to the passed in parameters
*
* @param databaseName {@see setDatabaseName}
* @param scriptLocations {@see setScriptLocations}
* @param persist {@see setPersist}
* @param executeScriptQuery {@see setLoadDataQuery}
* @param dateConversionPatterns {@see setDateConversionPatterns}
*/
public H2DataSourceFactory(String databaseName, List<Resource> scriptLocations, Boolean persist, String executeScriptQuery, Map<String, String> dateConversionPatterns) {
setDatabaseName(databaseName);
setScriptLocations(scriptLocations);
setPersist(persist);
setExecuteScriptQuery(executeScriptQuery);
setDateConversionPatterns(dateConversionPatterns);
}
/**
* Optional property
* <p/>
* Sets a map of conversion patterns to register with the Oracle conversion utilities
*
* @param dateConversionPatterns map of patterns keyed by Oracle syntax with a value of a {@link java.text.DateFormat}
*/
public void setDateConversionPatterns(Map<String, String> dateConversionPatterns) {
this.dateConversionPatterns = dateConversionPatterns;
}
/**
* Optional Property
* <p/>
* Sets whether or not to persist the database
*
* @param persist boolean value
*/
public void setPersist(Boolean persist) {
this.persist = persist;
}
/**
* Optional Property
* <p/>
* Set the query used to determine whether or not to execute the scripts on initialization
*
* @param executeScriptQuery the query to execute. If there are no results of the query, the scripts referenced
* in setScriptLocations will be executed. The statement must be a select statement.
*/
public void setExecuteScriptQuery(String executeScriptQuery) {
this.executeScriptQuery = executeScriptQuery;
}
/**
* Required Property
* <p/>
* Sets the name of the in memory database/schema
*
* @param databaseName the name such as "mymii"
*/
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
/**
* Required Property
* <p/>
* Sets the locations of the files containing DDL to be executed against the database
* <p/>
* NOTE: Files are executed in order
*
* @param scriptLocations list of {@link Resource} compatible location strings
*/
public void setScriptLocations(List<Resource> scriptLocations) {
this.scriptLocations = scriptLocations;
}
@PostConstruct
public void initializeFactory() {
validateProperties();
}
public Object getObject() throws Exception {
return getDataSource();
}
public Class getObjectType() {
return DataSource.class;
}
public boolean isSingleton() {
return true;
}
/**
* Gets the singleton DataSource if created. Initializes if not already created.
*
* @return the DataSource
*/
public DataSource getDataSource() {
if (dataSource == null) {
initializeDataSource();
}
return dataSource;
}
/*
Helper methods
*/
protected void validateProperties() {
if (databaseName == null) {
throw new IllegalArgumentException("The name of the test database to create is required");
}
if (scriptLocations == null) {
throw new IllegalArgumentException("The path to the database schema DDL is required");
}
if(persist == null) {
persist = false;
}
}
protected void initializeDataSource() {
this.dataSource = createDataSource();
populateDataSourceIfNecessary();
}
protected DataSource createDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl(getConnectionString());
ds.setUsername("sa");
ds.setPassword("");
logger.debug("Created dataSource: " + ds.toString());
return ds;
}
protected String getConnectionString() {
return persist ?
"jdbc:h2:file:" + databaseName + ";MODE=MySQL" :
"jdbc:h2:mem:" + databaseName + ";MODE=MySQL;DB_CLOSE_DELAY=-1";
}
protected void populateDataSourceIfNecessary() {
Connection conn = null;
try {
conn = dataSource.getConnection();
if (!persist || testExecuteScriptQuery(conn, executeScriptQuery)) {
logger.debug("Database is empty. Loading script files");
executeScripts(conn, scriptLocations);
}
} catch (SQLException e) {
logger.error("Error querying or populating database", e);
throw new RuntimeException(e);
} finally {
closeConnection(conn);
}
}
/*
Static Helper methods
*/
protected static void executeScripts(Connection connection, List<Resource> resources) {
for (Resource script : resources) {
try {
String sql = new SqlFileParser(script).getSQL();
logger.debug("Executing sql:\n" + sql);
executeSql(sql, connection);
logger.debug("Successfully executed statement");
} catch (IOException e) {
throw new RuntimeException("File IO Exception while loading " + script.getFilename(), e);
} catch (SQLException e) {
throw new RuntimeException("SQL exception occurred loading data from " + script.getFilename(), e);
}
}
}
protected static boolean testExecuteScriptQuery(Connection conn, String executeScriptQuery) {
boolean result;
try {
//If the ResultSet has any rows, the first method will return true
result = !executeQuery(conn, executeScriptQuery).first();
} catch (SQLException e) {
//Only return true if the exception we got is that the table was not found
result = e.getMessage().toLowerCase().matches("table \".*\" not found.*\n*.*");
}
logger.debug("Executed query " + executeScriptQuery + " with result " + result);
return result;
}
protected static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error("Error closing connection to database", e);
}
}
}
protected static ResultSet executeQuery(Connection conn, String executeScriptQuery) throws SQLException {
Statement statement = conn.createStatement();
return statement.executeQuery(executeScriptQuery);
}
protected static void executeSql(String sql, Connection connection) throws SQLException {
Statement statement = connection.createStatement();
statement.execute(sql);
}
}

View File

@ -0,0 +1,141 @@
package org.mitre.jdbc.datasource.util;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Matt Franklin
*
* Parses a file looking for create, alter, insert, update, delete or
* drop commands and appends them to an output string or follows @@
* syntax to read child scripts.
*
*/
public class SqlFileParser {
private static final Pattern WORD_PATTERN = Pattern
.compile("^([a-zA-Z]*)[ ;]");
private static final String CHILD_SCRIPT_INDICATOR = "@@";
private static final Set<String> commandSet;
static {
commandSet = new HashSet<String>();
commandSet.add("create");
commandSet.add("alter");
commandSet.add("insert");
commandSet.add("update");
commandSet.add("drop");
commandSet.add("delete");
commandSet.add("commit");
commandSet.add("set");
commandSet.add("truncate");
commandSet.add("rollback");
}
private enum State {
INIT, READFILE, READSQL
}
private Stack<State> stateStack;
private Resource resource;
/**
* Constructor takes a Spring {@link Resource}
*
* @param resource
* the initial file to parse
*/
public SqlFileParser(Resource resource) {
stateStack = new Stack<State>();
this.resource = resource;
}
/**
* Gets the executable SQL statements from the resource passed to the
* constructor and its children
*
* @return a valid executable string containing SQL statements
* @throws IOException
* if the resource or its children are not found
*/
public String getSQL() throws IOException {
return processResource(resource);
}
private String processResource(Resource res) throws IOException {
StringBuilder sql = new StringBuilder();
File resourceFile = res.getFile();
stateStack.push(State.INIT);
processFile(resourceFile, sql);
stateStack.pop();
return sql.toString();
}
private void processFile(File file, StringBuilder sql) throws IOException {
BufferedReader fileReader = new BufferedReader(new FileReader(
file.getAbsoluteFile()));
String line = null;
while ((line = fileReader.readLine()) != null) {
processLine(sql, line);
}
}
private void processLine(StringBuilder sql, String line) throws IOException {
String lowerLine = line.toLowerCase().trim();
switch (stateStack.peek()) {
case INIT: {
if (lowerLine.startsWith(CHILD_SCRIPT_INDICATOR)) {
// replace the current element in the stack with the new state
stateStack.pop();
stateStack.push(State.READFILE);
processLine(sql, line);
} else if (commandSet.contains(getFirstWord(lowerLine))) {
// replace the current element in the stack with the new state
stateStack.pop();
stateStack.push(State.READSQL);
processLine(sql, line);
}
break;
}
case READFILE: {
stateStack.push(State.INIT);
Resource child = resource.createRelative(line.replace(
CHILD_SCRIPT_INDICATOR, ""));
sql.append(processResource(child));
// Read File lines do not have a terminal character but are by
// definition only one line Long
stateStack.pop();
stateStack.push(State.INIT);
break;
}
case READSQL: {
sql.append(line);
// add a space to accommodate line breaks. Not a big deal if
// extraneous spaces are added
sql.append(" ");
if (lowerLine.endsWith(";")) {
stateStack.pop();
stateStack.push(State.INIT);
}
break;
}
default: {
throw new RuntimeException("Invalid State");
}
}
}
private static String getFirstWord(String line) {
Matcher match = WORD_PATTERN.matcher(line);
return match.find() ? match.group(1) : null;
}
}

View File

@ -7,6 +7,7 @@ import javax.persistence.PersistenceContext;
import org.mitre.openid.connect.model.Address;
import org.mitre.openid.connect.repository.AddressRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Michael Joseph Walsh
*
*/
@Repository
public class JpaAddressRepository implements AddressRepository {
@PersistenceContext

View File

@ -13,6 +13,7 @@ import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.openid.connect.model.ApprovedSite;
import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.repository.ApprovedSiteRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
@ -21,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Michael Joseph Walsh
*
*/
@Repository
public class JpaApprovedSiteRepository implements ApprovedSiteRepository {
@PersistenceContext

View File

@ -7,6 +7,7 @@ import javax.persistence.PersistenceContext;
import org.mitre.openid.connect.model.Event;
import org.mitre.openid.connect.repository.EventRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Michael Joseph Walsh
*
*/
@Repository
public class JpaEventRepository implements EventRepository {
@PersistenceContext

View File

@ -7,6 +7,7 @@ import javax.persistence.PersistenceContext;
import org.mitre.openid.connect.model.IdTokenClaims;
import org.mitre.openid.connect.repository.IdTokenClaimsRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Michael Joseph Walsh
*
*/
@Repository
public class JpaIdTokenClaimsRepository implements IdTokenClaimsRepository {
@PersistenceContext

View File

@ -7,6 +7,7 @@ import javax.persistence.PersistenceContext;
import org.mitre.openid.connect.model.IdToken;
import org.mitre.openid.connect.repository.IdTokenRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Michael Joseph Walsh
*
*/
@Repository
public class JpaIdTokenRepository implements IdTokenRepository {
@PersistenceContext

View File

@ -10,8 +10,16 @@ import javax.persistence.TypedQuery;
import org.mitre.openid.connect.model.WhitelistedSite;
import org.mitre.openid.connect.repository.WhitelistedSiteRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* JPA WhitelistedSite repository implementation
*
* @author Michael Joseph Walsh
*
*/
@Repository
public class JpaWhitelistedSiteRepository implements WhitelistedSiteRepository {
@PersistenceContext

View File

@ -1,7 +1,7 @@
package org.mitre.openid.connect.service.impl;
import org.mitre.openid.connect.model.Address;
import org.mitre.openid.connect.repository.impl.JpaAddressRepository;
import org.mitre.openid.connect.repository.AddressRepository;
import org.mitre.openid.connect.service.AddressService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
public class AddressServiceImpl implements AddressService {
@Autowired
private JpaAddressRepository addressRepository;
private AddressRepository addressRepository;
/**
* Default constructor
@ -32,7 +32,7 @@ public class AddressServiceImpl implements AddressService {
*
* @param repository
*/
public AddressServiceImpl(JpaAddressRepository addressRepository) {
public AddressServiceImpl(AddressRepository addressRepository) {
this.addressRepository = addressRepository;
}

View File

@ -5,7 +5,7 @@ import java.util.Collection;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.openid.connect.model.ApprovedSite;
import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.repository.impl.JpaApprovedSiteRepository;
import org.mitre.openid.connect.repository.ApprovedSiteRepository;
import org.mitre.openid.connect.service.ApprovedSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -22,7 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
public class ApprovedSiteServiceImpl implements ApprovedSiteService {
@Autowired
private JpaApprovedSiteRepository approvedSiteRepository;
private ApprovedSiteRepository approvedSiteRepository;
/**
* Default constructor
@ -36,7 +36,7 @@ public class ApprovedSiteServiceImpl implements ApprovedSiteService {
*
* @param repository
*/
public ApprovedSiteServiceImpl(JpaApprovedSiteRepository approvedSiteRepository) {
public ApprovedSiteServiceImpl(ApprovedSiteRepository approvedSiteRepository) {
this.approvedSiteRepository = approvedSiteRepository;
}

View File

@ -1,7 +1,7 @@
package org.mitre.openid.connect.service.impl;
import org.mitre.openid.connect.model.Event;
import org.mitre.openid.connect.repository.impl.JpaEventRepository;
import org.mitre.openid.connect.repository.EventRepository;
import org.mitre.openid.connect.service.EventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
public class EventServiceImpl implements EventService {
@Autowired
private JpaEventRepository eventRepository;
private EventRepository eventRepository;
/**
* Default constructor
@ -32,7 +32,7 @@ public class EventServiceImpl implements EventService {
*
* @param repository
*/
public EventServiceImpl(JpaEventRepository eventRepository) {
public EventServiceImpl(EventRepository eventRepository) {
this.eventRepository = eventRepository;
}

View File

@ -1,7 +1,7 @@
package org.mitre.openid.connect.service.impl;
import org.mitre.openid.connect.model.IdTokenClaims;
import org.mitre.openid.connect.repository.impl.JpaIdTokenClaimsRepository;
import org.mitre.openid.connect.repository.IdTokenClaimsRepository;
import org.mitre.openid.connect.service.IdTokenClaimsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
public class IdTokenClaimsServiceImpl implements IdTokenClaimsService {
@Autowired
private JpaIdTokenClaimsRepository idTokenClaimsRepository;
private IdTokenClaimsRepository idTokenClaimsRepository;
/**
* Default constructor
@ -32,7 +32,7 @@ public class IdTokenClaimsServiceImpl implements IdTokenClaimsService {
*
* @param repository
*/
public IdTokenClaimsServiceImpl(JpaIdTokenClaimsRepository idTokenClaimsRepository) {
public IdTokenClaimsServiceImpl(IdTokenClaimsRepository idTokenClaimsRepository) {
this.idTokenClaimsRepository = idTokenClaimsRepository;
}

View File

@ -1,7 +1,7 @@
package org.mitre.openid.connect.service.impl;
import org.mitre.openid.connect.model.IdToken;
import org.mitre.openid.connect.repository.impl.JpaIdTokenRepository;
import org.mitre.openid.connect.repository.IdTokenRepository;
import org.mitre.openid.connect.service.IdTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
public class IdTokenServiceImpl implements IdTokenService {
@Autowired
private JpaIdTokenRepository idTokenRepository;
private IdTokenRepository idTokenRepository;
/**
* Default constructor
@ -32,7 +32,7 @@ public class IdTokenServiceImpl implements IdTokenService {
*
* @param repository
*/
public IdTokenServiceImpl(JpaIdTokenRepository idTokenRepository) {
public IdTokenServiceImpl(IdTokenRepository idTokenRepository) {
this.idTokenRepository = idTokenRepository;
}

View File

@ -1,7 +1,7 @@
package org.mitre.openid.connect.service.impl;
import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.repository.impl.JpaUserInfoRepository;
import org.mitre.openid.connect.repository.UserInfoRepository;
import org.mitre.openid.connect.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private JpaUserInfoRepository userInfoRepository;
private UserInfoRepository userInfoRepository;
/**
* Default constructor
@ -32,7 +32,7 @@ public class UserInfoServiceImpl implements UserInfoService {
*
* @param repository
*/
public UserInfoServiceImpl(JpaUserInfoRepository userInfoRepository) {
public UserInfoServiceImpl(UserInfoRepository userInfoRepository) {
this.userInfoRepository = userInfoRepository;
}

View File

@ -1,7 +1,7 @@
package org.mitre.openid.connect.service.impl;
import org.mitre.openid.connect.model.WhitelistedSite;
import org.mitre.openid.connect.repository.impl.JpaWhitelistedSiteRepository;
import org.mitre.openid.connect.repository.WhitelistedSiteRepository;
import org.mitre.openid.connect.service.WhitelistedSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
public class WhitelistedSiteServiceImpl implements WhitelistedSiteService {
@Autowired
private JpaWhitelistedSiteRepository whitelistedSiteRepository;
private WhitelistedSiteRepository whitelistedSiteRepository;
/**
* Default constructor
@ -33,7 +33,7 @@ public class WhitelistedSiteServiceImpl implements WhitelistedSiteService {
* @param repository
*/
public WhitelistedSiteServiceImpl(
JpaWhitelistedSiteRepository whitelistedSiteRepository) {
WhitelistedSiteRepository whitelistedSiteRepository) {
this.whitelistedSiteRepository = whitelistedSiteRepository;
}