package org.openmhealth.reference.data.sql;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import org.openmhealth.reference.data.AuthenticationTokenBin;
import org.openmhealth.reference.data.AuthorizationCodeBin;
import org.openmhealth.reference.data.ThirdPartyBin;
import org.openmhealth.reference.domain.AuthorizationCode;
import org.openmhealth.reference.domain.ThirdParty;
import org.openmhealth.reference.exception.OmhException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* <p>
* The SQL implementation of the interface to the database-backed collection of
* {@link AuthorizationCode}s.
* </p>
*
* @author John Jenkins
*/
public class SqlAuthorizationCodeBin
extends AuthorizationCodeBin
implements SqlDaoInterface {
/*
* (non-Javadoc)
* @see org.openmhealth.reference.data.AuthorizationCodeBin#storeCode(org.openmhealth.reference.domain.AuthorizationCode)
*/
@Override
public void storeCode(final AuthorizationCode code) throws OmhException {
// Validate the parameter.
if(code == null) {
throw new OmhException("The code is null.");
}
// Get the DAO.
SqlDao dao = SqlDao.getInstance();
// Get the transaction manager.
PlatformTransactionManager transactionManager =
dao.getTransactionManager();
// Create a new transaction definition and name it.
DefaultTransactionDefinition transactionDefinition =
new DefaultTransactionDefinition();
transactionDefinition
.setName("Adding an authorization code.");
// Create the new transaction.
TransactionStatus transactionStatus =
transactionManager.getTransaction(transactionDefinition);
// Get the JDBC template.
JdbcTemplate jdbcTemplate = dao.getJdbcTemplate();
// Create the binary representation of the data.
byte[] scopes;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(code.getScopes());
scopes = baos.toByteArray();
}
catch(IOException e) {
throw
new OmhException(
"There was an error creating the output stream.",
e);
}
// Add the authorization code.
try {
jdbcTemplate
.update(
"INSERT INTO " + AuthorizationCodeBin.DB_NAME +
" (" +
ThirdPartyBin.DB_NAME + "_id" + ", " +
AuthorizationCode.JSON_KEY_CODE + ", " +
AuthorizationCode.JSON_KEY_CREATION_TIME + ", " +
AuthorizationCode.JSON_KEY_EXPIRATION_TIME + ", " +
AuthorizationCode.JSON_KEY_SCOPES + ", " +
AuthorizationCode.JSON_KEY_STATE + " " +
") " +
"VALUES" +
" (" +
"(" +
"SELECT " + SqlDao.KEY_DATABASE_ID + " " +
"FROM " + ThirdPartyBin.DB_NAME + " " +
"WHERE " + ThirdParty.JSON_KEY_ID + " = ?" +
"), " +
"?, " +
"?, " +
"?, " +
"?, " +
"?" +
")",
new Object[] {
code.getThirdPartyId(),
code.getCode(),
code.getCreationTime(),
code.getExpirationTime(),
scopes,
code.getState()
}
);
// Commit the transaction
transactionManager.commit(transactionStatus);
}
catch(DataAccessException e) {
transactionManager.rollback(transactionStatus);
throw
new OmhException(
"There was a problem storing the authorization code.",
e);
}
}
/*
* (non-Javadoc)
* @see org.openmhealth.reference.data.AuthorizationCodeBin#getCode(java.lang.String)
*/
@Override
public AuthorizationCode getCode(final String code) throws OmhException {
// Validate the parameter.
if(code == null) {
throw new OmhException("The code is null.");
}
try {
return
SqlDao
.getInstance()
.getJdbcTemplate()
.queryForObject(
"SELECT " +
ThirdParty.JSON_KEY_ID + ", " +
AuthorizationCode.JSON_KEY_CODE + ", " +
AuthorizationCode.JSON_KEY_CREATION_TIME +
", " +
AuthorizationCode.JSON_KEY_EXPIRATION_TIME +
", " +
AuthorizationCode.JSON_KEY_SCOPES + ", " +
AuthorizationCode.JSON_KEY_STATE + " " +
"FROM " +
ThirdPartyBin.DB_NAME + ", " +
AuthorizationCodeBin.DB_NAME + " " +
"WHERE " +
ThirdPartyBin.DB_NAME +
"." +
SqlDao.KEY_DATABASE_ID +
" = " +
AuthorizationCodeBin.DB_NAME +
"." +
ThirdPartyBin.DB_NAME + "_id " +
"AND " +
AuthorizationCode.JSON_KEY_CODE + " = ?",
new Object[] { code },
new RowMapper<AuthorizationCode>() {
/**
* Maps the row to an {@link AuthorizationCode}
* object.
*/
@SuppressWarnings("unchecked")
@Override
public AuthorizationCode mapRow(
final ResultSet resultSet,
final int rowNum)
throws SQLException {
// Decode the scopes byte array.
Set<String> scopes;
try {
byte[] scopesArray =
resultSet
.getBytes(
AuthorizationCode
.JSON_KEY_SCOPES);
ByteArrayInputStream bais =
new ByteArrayInputStream(scopesArray);
ObjectInputStream ois =
new ObjectInputStream(bais);
scopes = (Set<String>) ois.readObject();
}
catch(IOException e) {
throw
new SQLException(
"The scopes object could not be " +
"read or decoded.",
e);
}
catch(ClassNotFoundException e) {
throw
new SQLException(
"The Set class is unknown.",
e);
}
return
new AuthorizationCode(
resultSet
.getString(ThirdParty.JSON_KEY_ID),
resultSet
.getString(
AuthorizationCode
.JSON_KEY_CODE),
resultSet
.getLong(
AuthorizationCode
.JSON_KEY_CREATION_TIME),
resultSet
.getLong(
AuthorizationCode
.JSON_KEY_EXPIRATION_TIME),
scopes,
resultSet
.getString(
AuthorizationCode
.JSON_KEY_STATE));
}
});
}
// If the problem is that the number of results isn't what we expected,
// we may still be alright.
catch(IncorrectResultSizeDataAccessException e) {
// If there weren't any tokens with the given token value, then we
// simply return null.
if(e.getActualSize() == 0) {
return null;
}
// Otherwise, we throw an exception.
throw
new OmhException(
"Multiple authorization codes have the same value.",
e);
}
// For all other issues, we simply propagate the exception.
catch(DataAccessException e) {
throw
new OmhException(
"There was an error querying for an authorization codes.",
e);
}
}
/*
* (non-Javadoc)
* @see org.openmhealth.reference.data.sql.SqlDaoInterface#getSqlTableDefinition()
*/
@Override
public String getSqlTableDefinition() {
return
// Create the table if it doesn't exist.
"CREATE TABLE IF NOT EXISTS " +
AuthorizationCodeBin.DB_NAME + "(" +
// Create the database ID.
SqlDao.KEY_DATABASE_ID +
" int unsigned NOT NULL auto_increment, " +
// Link it to the third-party table.
ThirdPartyBin.DB_NAME + "_id int unsigned NOT NULL, " +
// Store the code.
AuthorizationCode.JSON_KEY_CODE +
" varchar(36) NOT NULL, " +
// Store the creation time.
AuthorizationCode.JSON_KEY_CREATION_TIME +
" bigint NOT NULL, " +
// Store the expiration time.
AuthorizationCode.JSON_KEY_EXPIRATION_TIME +
" bigint NOT NULL, " +
// Store the scope as a string that Java can easily encode
// and decode.
AuthorizationCode.JSON_KEY_SCOPES +
" blob NOT NULL, " +
// Store the state.
// This is being saved as a VARCHAR, which may not be
// sufficient, but it is more efficient than TEXT.
AuthorizationCode.JSON_KEY_STATE +
" varchar(255), " +
// Create the primary key.
"PRIMARY KEY (" + SqlDao.KEY_DATABASE_ID + "), " +
// Ensure that all codes are unique.
"UNIQUE INDEX " +
"`" +
AuthenticationTokenBin.DB_NAME +
"_unique_index_" +
AuthorizationCode.JSON_KEY_CODE +
"` " +
"(" + AuthorizationCode.JSON_KEY_CODE + "), " +
// Link to the third-party table.
"CONSTRAINT " +
"`" +
AuthorizationCodeBin.DB_NAME +
"_fk_" +
ThirdPartyBin.DB_NAME + "_id" +
"` " +
"FOREIGN KEY " +
"`" +
AuthorizationCodeBin.DB_NAME +
"_index_" +
ThirdPartyBin.DB_NAME + "_id" +
"` " +
"(" + ThirdPartyBin.DB_NAME + "_id) " +
"REFERENCES " +
ThirdPartyBin.DB_NAME + " " +
"(" + SqlDao.KEY_DATABASE_ID + ") " +
"ON UPDATE CASCADE " +
"ON DELETE CASCADE" +
")";
}
}