/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.api.internal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.openbel.framework.common.BELUtilities;
import org.openbel.framework.common.InvalidArgument;
import org.openbel.framework.common.cfg.SystemConfiguration;
import org.openbel.framework.core.df.AbstractJdbcDAO;
import org.openbel.framework.core.df.DBConnection;
import org.openbel.framework.api.AnnotationFilterCriteria;
import org.openbel.framework.api.BelDocumentFilterCriteria;
import org.openbel.framework.api.CitationFilterCriteria;
import org.openbel.framework.api.Filter;
import org.openbel.framework.api.FilterCriteria;
import org.openbel.framework.api.KamStoreObjectImpl;
import org.openbel.framework.api.NamespaceFilterCriteria;
import org.openbel.framework.api.RelationshipTypeFilterCriteria;
/**
* KAMCatalogDao provides a JDBC-driven DAO for accessing the KAM catalog
* of the KAMStore.
*
* @author Julian Ray {@code jray@selventa.com}
*/
public final class KAMCatalogDao extends AbstractJdbcDAO {
/**
* The SQL query to select all {@link KamInfo} objects from the KAM catalog.
*/
private static final String SELECT_KAM_CATALOG_SQL =
"SELECT " +
"kam_id, name, description, last_compiled, schema_name " +
"FROM " +
"@.kam " +
"ORDER BY name";
/**
* The SQL query to select a {@link KamInfo} object by kam name.
*/
private static final String SELECT_KAM_BY_NAME_SQL =
"SELECT " +
"kam_id, name, description, last_compiled, schema_name " +
"FROM " +
"@.kam " +
"WHERE " +
"name = ?";
/**
* The SQL query to select a {@link KamInfo} object by kam id.
*/
private static final String SELECT_KAM_BY_ID_SQL =
"SELECT " +
"kam_id, name, description, last_compiled, schema_name " +
"FROM " +
"@.kam " +
"WHERE " +
"kam_id = ?";
/**
* The SQL statement to insert a KAM into the KAM catalog.
*/
private static final String INSERT_KAM_SQL =
"INSERT INTO " +
"@.kam(name, description, last_compiled, schema_name) " +
"VALUES(?, ?, ?, ?)";
/**
* The SQL statement to update a KAM in the KAM catalog.
*/
private static final String UPDATE_KAM_SQL =
"UPDATE "
+
"@.kam "
+
"SET name = ?, description = ?, last_compiled = ?, schema_name = ? "
+
"WHERE " +
"kam_id = ?";
/**
* The SQL statement to delete a KAM from the KAM catalog.
*/
private static final String DELETE_KAM_SQL =
"DELETE " +
"FROM @.kam " +
"WHERE " +
"kam_id = ?";
private final String kamSchemaPrefix;
/**
* Creates a KAMStoreDaoImpl from the Jdbc {@link Connection} that will
* be used to load the KAM.
*
* @param dbc {@link Connection}, the database connection which should be
* non-null and already open for sql execution.
* @param kamCatalogSchema {@link String}, the kam catalog schema
* @throws InvalidArgument Thrown if {@code dbc} is null or the sql
* connection is already closed.
* @throws SQLException Thrown if a sql error occurred while loading
* the KAM.
*/
public KAMCatalogDao(DBConnection dbc, String kamCatalogSchema,
String kamSchemaPrefix) throws SQLException {
super(dbc, kamCatalogSchema);
if (StringUtils.isBlank(kamCatalogSchema)) {
throw new InvalidArgument("kamCatalogSchema is not set");
}
if (StringUtils.isBlank(kamSchemaPrefix)) {
throw new InvalidArgument("kamSchemaPrefix is not set");
}
if (dbc == null) {
throw new InvalidArgument("dbc is null");
}
if (dbc.getConnection().isClosed()) {
throw new InvalidArgument("dbc is closed and cannot be used");
}
this.kamSchemaPrefix = kamSchemaPrefix;
}
/**
* Retrieves the {@link KamInfo} objects from the KAM catalog database.
*
* <p>
* If the <tt>kam</tt> doesn't exist then a null {@link KamInfo} is returned.
* </p>
* @return {@link KamInfo}, the kam info object from the
* kam catalog database or null if the kam name cannot be found.
* @throws SQLException Thrown if a SQL error occurred while retrieving
* the {@link KamInfo} objects from the kam catalog.
*/
public List<KamInfo> getCatalog() throws SQLException {
List<KamInfo> list = new ArrayList<KamInfo>();
ResultSet rset = null;
try {
PreparedStatement ps = getPreparedStatement(SELECT_KAM_CATALOG_SQL);
rset = ps.executeQuery();
while (rset.next()) {
list.add(getKamInfo(rset));
}
} catch (SQLException ex) {
throw ex;
} finally {
close(rset);
}
return list;
}
/**
* Retrieves a {@link KamInfo} object, from the KAM catalog database,
* using the KAM's name.
*
* @param name {@link String}, the KAM name
* @return {@link KamInfo}, the queried KAM or null if no KAM is found by
* the <tt>name</tt>
* @throws SQLException Thrown if a SQL error occurred while retrieving
* the {@link KamInfo} object by kam name.
*/
public KamInfo getKamInfoByName(String kamName) throws SQLException {
KamInfo kamInfo = null;
ResultSet rset = null;
try {
PreparedStatement ps = getPreparedStatement(SELECT_KAM_BY_NAME_SQL);
ps.setString(1, kamName);
rset = ps.executeQuery();
if (rset.next()) {
kamInfo = getKamInfo(rset);
}
} catch (SQLException ex) {
throw ex;
} finally {
close(rset);
}
return kamInfo;
}
/**
* Retrieves a {@link KamInfo} object, from the KAM catalog database,
* using the KAM's id.
*
* @param id, the KAM id
* @return {@link KamInfo}, the queried KAM or null if no KAM is found by
* that <tt>id</tt>
* @throws SQLException Thrown if a SQL error occurred while retrieving
* the {@link KamInfo} object by kam id.
*/
public KamInfo getKamInfoById(final int id) throws SQLException {
KamInfo kamInfo = null;
ResultSet rset = null;
try {
PreparedStatement ps = getPreparedStatement(SELECT_KAM_BY_ID_SQL);
ps.setInt(1, id);
rset = ps.executeQuery();
if (rset.next()) {
kamInfo = getKamInfo(rset);
}
} catch (SQLException ex) {
throw ex;
} finally {
close(rset);
}
return kamInfo;
}
/**
* Saves the {@link KamDbObject} object to the KAM catalog database.
*
* <p>
* If the <tt>kamDb</tt> doesn't exist then create it, otherwise update
* the record's information. This method will look for an existing
* <tt>kamDb</tt> first by id, if that is not <tt>null</tt>, then
* by name. It can be used to update the name of the record, but it
* cannot be used to update the id.
* </p>
*
* @param kamInfo {@link KamDbObject}, the kam info to save to the kam catalog,
* which cannot be null, and must contain a non-null name
* @throws SQLException Thrown if a SQL error occurred saving the kam info
* to the kam catalog
* @throws InvalidArgument Thrown if <tt>kamDb</tt> is null or contains
* a null name.
*/
public void saveToCatalog(KamDbObject updated) throws SQLException {
if (updated == null) {
throw new InvalidArgument("kamInfo", updated);
}
if (updated.getName() == null) {
throw new InvalidArgument("kamInfo contains a null name");
}
if (updated.getDescription() == null) {
throw new InvalidArgument("kamInfo contains a null description");
}
// First check to see if the KAM already exists in the Catalog. This
// returns the name of the schema for the KAM or null if the KAM is
// not already there. The existence of KAMs is checked first by id
// then by name.
KamInfo originalInfo = null;
final Integer updatedId = updated.getId();
if (updatedId != null) {
originalInfo = getKamInfoById(updatedId.intValue());
}
if (originalInfo == null) {
originalInfo = getKamInfoByName(updated.getName());
}
// If the KAM exists we update the current catalog entry
try {
if (null != originalInfo) {
KamDbObject original = originalInfo.getKamDbObject();
updated.setSchemaName(original.getSchemaName());
//must update the kam info record.
PreparedStatement skips = getPreparedStatement(UPDATE_KAM_SQL);
skips.setString(1, updated.getName());
skips.setString(2, updated.getDescription());
skips.setTimestamp(3, new Timestamp(updated.getLastCompiled()
.getTime()));
skips.setString(4, original.getSchemaName());
skips.setInt(5, original.getId());
skips.execute();
} else {
// Otherwise we insert a new kam. Schema name is automatically
// generated
// find next available schema name
String schemaName = findNextSchemaName();
updated.setSchemaName(schemaName);
PreparedStatement ps = getPreparedStatement(INSERT_KAM_SQL,
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, updated.getName());
ps.setString(2, updated.getDescription());
ps.setTimestamp(3, new Timestamp(updated.getLastCompiled()
.getTime()));
ps.setString(4, updated.getSchemaName());
ps.execute();
}
} catch (SQLException ex) {
throw ex;
}
}
/**
* Deletes the {@link KamInfo} object with a provided name from the KAM catalog database.
*
* @param kamName, the name of a kam info object to delete from the kam catalog
* @throws SQLException Thrown if a SQL error occurred deleting the kam info object
* from the kam catalog
*/
public void deleteFromCatalog(final String kamName) throws SQLException {
if (kamName != null) {
KamInfo kamInfo = getKamInfoByName(kamName);
if (null != kamInfo) {
try {
PreparedStatement ps = getPreparedStatement(DELETE_KAM_SQL);
ps.setInt(1, kamInfo.getId());
ps.execute();
} catch (SQLException ex) {
throw ex;
}
}
}
}
/**
* Find the next available schema name in the KAM catalog based on the
* {@link SystemConfiguration#getKamSchemaPrefix() KAM schema prefix}.
*
* @return the next available schema name
* @throws SQLException Thrown if the SQL query to the KAM catalog failed
*/
private String findNextSchemaName() throws SQLException {
ResultSet rset = null;
try {
// Find next available schema name.
PreparedStatement ps = getPreparedStatement(SELECT_KAM_CATALOG_SQL);
rset = ps.executeQuery();
int maxId = 0;
while (rset.next()) {
// read schemaName value, skip if it does not look like a
// schema prefix
String schema = rset.getString(5);
if (schema == null || !schema.startsWith(kamSchemaPrefix)) {
continue;
}
// extract the schema number
String id = schema.substring(schema.indexOf(kamSchemaPrefix)
+ kamSchemaPrefix.length());
// if the schema number is numeric, compare to current max
if (StringUtils.isNumeric(id)) {
maxId = Math.max(Integer.parseInt(id), maxId);
}
}
String schemaName = kamSchemaPrefix + (++maxId);
return schemaName;
} catch (SQLException ex) {
throw ex;
} finally {
close(rset);
}
}
/**
*
* @param rset
* @return
* @throws SQLException
*/
private KamInfo getKamInfo(ResultSet rset) throws SQLException {
Integer kamId = rset.getInt(1);
String name = rset.getString(2);
String description = rset.getString(3);
// handle timestamp as date+time
Timestamp cts = rset.getTimestamp(4);
Date lastCompiled = null;
if (cts != null) {
lastCompiled = new Date(cts.getTime());
}
String schemaName = rset.getString(5);
return new KamInfo(new KamDbObject(kamId, name, description,
lastCompiled, schemaName));
}
/**
*
* @author julianjray
*
*/
public static class KamInfo extends KamStoreObjectImpl {
private final KamDbObject kamDb;
/**
* Precalculate the kam info hash code since the object is immutable.
*/
private final int hashCode;
public KamInfo(KamDbObject kamDb) {
super(kamDb.getId()); //kamDb must not be null
this.kamDb = kamDb;
if (kamDb.getId() == null) {
throw new InvalidArgument(
"KamDbObject and KamDbObject Id cannot be null.");
}
this.hashCode = generateHashCode();
}
public KamDbObject getKamDbObject() {
return kamDb;
}
private int generateHashCode() {
return 31 * this.getId().hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (o instanceof KamInfo) {
KamInfo ki = (KamInfo) o;
return this.getId().equals(ki.getId());
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return hashCode;
}
/**
*
* @return
*/
public String getSchemaName() {
return kamDb.getSchemaName();
}
/**
*
* @return
*/
public Date getLastCompiled() {
return kamDb.getLastCompiled();
}
/**
*
* @return
*/
public String getDescription() {
return kamDb.getDescription();
}
/**
*
* @return
*/
public String getName() {
return kamDb.getName();
}
/**
* @return
* @throws InvalidArgument
*/
public AnnotationFilter createAnnotationFilter() throws InvalidArgument {
return new AnnotationFilter(this);
}
/**
* @return
* @throws InvalidArgument
*/
public NamespaceFilter createNamespaceFilter() throws InvalidArgument {
return new NamespaceFilter(this);
}
/**
* @return
* @throws InvalidArgument
*/
public KamFilter createKamFilter() throws InvalidArgument {
return new KamFilter(this);
}
}
/**
* @author julianjray
*/
public static class AnnotationFilter extends Filter {
private AnnotationFilter(KamInfo kamInfo) {
super(kamInfo);
}
/**
* Adds a new AnnotationFilterCriteria to the Filter
*
* @param annotationFilterCriteria
*/
public void add(AnnotationFilterCriteria annotationFilterCriteria) {
getFilterCriteria().add(annotationFilterCriteria);
}
}
/**
* @author julianjray
*/
public static class NamespaceFilter extends Filter {
private NamespaceFilter(KamInfo kamInfo) {
super(kamInfo);
}
/**
* Adds a new NamespaceFilterCritera to the Filter
*
* @param criteria
*/
public void add(NamespaceFilterCriteria criteria) {
getFilterCriteria().add(criteria);
}
}
/**
* @author julianjray
*/
public static class CitationFilter extends Filter {
private CitationFilter(KamInfo kamInfo) {
super(kamInfo);
}
/**
* Adds a new CitationFilterCriteria to the Filter
*
* @param citationFilterCriteria
*/
public void add(CitationFilterCriteria citationFilterCriteria) {
getFilterCriteria().add(citationFilterCriteria);
}
}
/**
* @author julianjray
*/
public static class KamFilter extends Filter {
private KamFilter(KamInfo kamInfo) {
super(kamInfo);
}
public void add(CitationFilterCriteria citationFilterCriteria) {
getFilterCriteria().add(citationFilterCriteria);
}
/**
* Adds a new AnnotationFilterCriteria to the Filter
*
* @param annotationFilterCriteria
*/
public void add(AnnotationFilterCriteria annotationFilterCriteria) {
getFilterCriteria().add(annotationFilterCriteria);
}
/**
* Adds a new RelationshipTypeFilterCriteria to the Filter
*
* @param relationshipTypeFilterCriteria
*/
public void add(
RelationshipTypeFilterCriteria relationshipTypeFilterCriteria) {
getFilterCriteria().add(relationshipTypeFilterCriteria);
}
/**
* Adds a new BelDocumentFilterCriteria to the Filter
*
* @param belDocumentFilterCriteria
*/
public void add(BelDocumentFilterCriteria belDocumentFilterCriteria) {
getFilterCriteria().add(belDocumentFilterCriteria);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj == null) {
return false;
} else if (!(obj instanceof KamFilter)) {
return false;
} else {
KamFilter other = (KamFilter) obj;
return (BELUtilities.equals(getKamInfo(), other.getKamInfo()) && BELUtilities
.equals(getFilterCriteria(), other.getFilterCriteria()));
}
}
@Override
public int hashCode() {
final int prime = 31;
int hash = 0;
int criteriaHash = 0;
final List<FilterCriteria> criteria = getFilterCriteria();
if (criteria != null) {
for (FilterCriteria criterion : criteria) {
criteriaHash ^= criterion.hashCode();
}
}
hash += getKamInfo().hashCode();
hash *= prime;
hash += criteriaHash;
return hash;
}
}
}