/* * Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute * Copyright [2016-2017] EMBL-European Bioinformatics Institute * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (C) 2003 EBI, GRL * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.ensembl.healthcheck; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.ensembl.healthcheck.util.CollectionUtils; import org.ensembl.healthcheck.util.ConnectionBasedSqlTemplateImpl; import org.ensembl.healthcheck.util.ConnectionPool; import org.ensembl.healthcheck.util.DBUtils; import org.ensembl.healthcheck.util.RowMapper; import org.ensembl.healthcheck.util.SqlTemplate; import org.ensembl.healthcheck.util.SqlUncheckedException; import org.ensembl.healthcheck.util.UtilUncheckedException; /** * Container for information about a database that can be stored in a * DatabaseRegistry. */ public class DatabaseRegistryEntry implements Comparable<DatabaseRegistryEntry> { private static final String COLLECTION_CLAUSE = "_collection"; /** * Simple read-only bean to store pertinent information about a database. * Objects of this type are held by the {@link DatabaseRegistryEntry} and * attached to {@link ReportLine} objects to improve reporting * * @author dstaines */ public static class DatabaseInfo { private final String name; private final String alias; private final Species species; private final DatabaseType type; private final String schemaVersion; private final String genebuildVersion; /** * Constructor to set up key properties of {@link DatabaseInfo} * * @param name * @param species * @param type * @param schemaVersion */ public DatabaseInfo(String name, String alias, Species species, DatabaseType type, String schemaVersion, String genebuildVersion) { this.name = name; this.alias = alias; this.species = species; this.type = type; this.schemaVersion = schemaVersion; this.genebuildVersion = genebuildVersion; } public String getName() { return name; } public String getAlias() { return alias; } public Species getSpecies() { return species; } public DatabaseType getType() { return type; } public String getSchemaVersion() { return schemaVersion; } public String getGenebuildVersion() { return genebuildVersion; } public String toString() { return ReflectionToStringBuilder.toString(this); } } // e.g. username_species_type protected final static Pattern GB_DB = Pattern .compile("^[a-z0-9]+_([a-z]+)_([A-Za-z]+)"); // e.g. neurospora_crassa_core_4_56_1a protected final static Pattern EG_DB = Pattern .compile("^([a-zA-Z0-9_]+)_([a-z]+)_[0-9]+_([0-9]+)_([0-9A-Za-z]+)"); // e.g. homo_sapiens_core_56_37a protected final static Pattern E_DB = Pattern .compile("^([a-z]+_[a-z0-9]+(?:_[a-z0-9]+)?)_([a-z]+)_([0-9]+)_([0-9A-Za-z]+)"); // e.g. prefix_homo_sapiens_funcgen_60_37e protected final static Pattern PE_DB = Pattern .compile("^[^_]+_([^_]+_[^_]+)_([a-z]+)_([0-9]+)_([0-9A-Za-z]+)"); // human_core_20, hsapiens_XXX protected final static Pattern EEL_DB = Pattern .compile("^([^_]+)_([a-z]+)_([0-9]+)"); // ensembl_compara_bacteria_3_56 protected final static Pattern EGC_DB = Pattern .compile("^(ensembl)_(compara)_[a-z_]+_[0-9]+_([0-9]+)"); // ensembl_compara_56 protected final static Pattern EC_DB = Pattern .compile("^(ensembl)_(compara)_([0-9]+)"); // username_ensembl_compara_57 protected final static Pattern UC_DB = Pattern .compile("^[^_]+_(ensembl)_(compara)_([0-9]+)"); // username_ensembl_compara_57 protected final static Pattern UCM_DB = Pattern .compile("^[^_]+_(ensembl)_(compara)_master"); // username_ensembl_ancestral_57 protected final static Pattern EA_DB = Pattern .compile("^(ensembl)_(ancestral)_([0-9]+)"); // username_ensembl_ancestral_57 protected final static Pattern UA_DB = Pattern .compile("^[^_]+_(ensembl)_(ancestral)_([0-9]+)"); // ensembl_mart_56 protected final static Pattern EM_DB = Pattern .compile("^([a-z_]+)_(mart)_([0-9])+"); // username_species_type_version_release protected final static Pattern V_DB = Pattern .compile("vega_([^_]+_[^_]+)_[^_]+_([^_]+)_([^_]+)"); protected final static Pattern EE_DB = Pattern .compile("^([^_]+_[^_]+)_[a-z]+_([a-z]+)_[a-z]+_([0-9]+)_([0-9A-Za-z]+)"); // username_species_type_version_release protected final static Pattern U_DB = Pattern .compile("^[^_]+_([^_]+_[^_]+)_([a-z]+)_([0-9]+)_([0-9A-Za-z]+)"); protected final static Pattern HELP_DB = Pattern .compile("^(ensembl)_(help)_([0-9]+)"); protected final static Pattern EW_DB = Pattern .compile("^(ensembl)_(website)_([0-9]+)"); protected final static Pattern TAX_DB = Pattern .compile("^(ncbi)_(taxonomy)_([0-9]+)"); protected final static Pattern UD_DB = Pattern .compile("^([a-z_]+)_(userdata)"); protected final static Pattern BLAST_DB = Pattern .compile("^([a-z_]+)_(blast)"); protected final static Pattern MASTER_DB = Pattern .compile("^(master_schema)_([a-z]+)?_([0-9]+)"); protected final static Pattern MYSQL_DB = Pattern .compile("^(mysql|information_schema)"); protected final static Pattern[] patterns = { EC_DB, UA_DB, UC_DB, UCM_DB, EA_DB, EGC_DB, EG_DB, E_DB, PE_DB, EM_DB, EE_DB, EEL_DB, U_DB, V_DB, MYSQL_DB, BLAST_DB, UD_DB, TAX_DB, EW_DB, HELP_DB, GB_DB, MASTER_DB }; /** * Utility for building a {@link DatabaseInfo} object given a name * * @param name * @return object containing information about a database */ public static DatabaseInfo getInfoFromName(String name) { return getInfoFromName(name, null, null); } /** * <p> * Returns information about a database. Queries the meta table to determine * the type and schema version of the database. * </p> * * @param server * @param name * @return DatabaseInfo */ public static DatabaseInfo getInfoFromDatabase(DatabaseServer server, final String name) throws SQLException { SqlTemplate template = null; try { template = new ConnectionBasedSqlTemplateImpl( server.getDatabaseConnection(name)); } catch (NullPointerException e) { // This exception can be thrown, if a database name has hashes in // it like this one: // // #mysql50#jhv_gadus_morhua_57_merged_projection_build.bak // or // #mysql50#jhv_gadus_morhua_57_ref_1.3_asm_buggy // // A database like this can exist on a MySql server, but // connecting to it will cause a NullPointerException to be // thrown. // logger.warning("Unable to connect to " + name + " on " + server); // No info will be available for this database. // return null; } DatabaseInfo info = null; boolean dbHasAMetaTable = template.queryForDefaultObjectList( "show tables like 'meta'", String.class).size() == 1; if (dbHasAMetaTable) { try { List<DatabaseInfo> dbInfos = template .queryForList( // Will return something like ("core", 63) // "select m1.meta_value, m2.meta_value from meta m1 join meta m2 where m1.meta_key='schema_type' and m2.meta_key='schema_version'", new RowMapper<DatabaseInfo>() { public DatabaseInfo mapRow( ResultSet resultSet, int position) throws SQLException { String schemaType = resultSet .getString(1); String schemaVersion = resultSet .getString(2); return new DatabaseInfo( name, null, Species.UNKNOWN, DatabaseType .resolveAlias(schemaType), schemaVersion, null); } }); info = CollectionUtils.getFirstElement(dbInfos, info); } catch (SqlUncheckedException e) { logger.warning("Can't determine database type and version from " + name + " on " + server+": "+e.getMessage()); // No info will be available for this database. // return null; } finally { } } return info; } /** * Utility for building a {@link DatabaseInfo} object given a name plus * optional {@link Species} and {@link DatabaseType} to use explicitly * * @param name * @param species * (optional) * @param type * (optional) * @return object containing information about a database */ public static DatabaseInfo getInfoFromName(String name, Species species, DatabaseType type) { String schemaVersion = null; String genebuildVersion = null; String alias = null; String typeStr = null; Matcher m; for (Pattern p : patterns) { m = p.matcher(name); if (m.matches()) { // group 1 = alias alias = m.group(1); if (m.groupCount() > 1) { // group 2 = type typeStr = m.group(2); if (m.groupCount() > 2) { // group 3 = schema_version schemaVersion = m.group(3); if (m.groupCount() > 3) { // group 4 = gb_version genebuildVersion = m.group(4); } } } if (alias.endsWith(COLLECTION_CLAUSE)) { alias = alias.replaceAll(COLLECTION_CLAUSE, StringUtils.EMPTY); } break; } } if (species == null) { if (!StringUtils.isEmpty(alias)) { species = Species.resolveAlias(alias); } else { species = Species.UNKNOWN; } } if (type == null) { if (!StringUtils.isEmpty(typeStr)) { type = DatabaseType.resolveAlias(typeStr); if (typeStr.equals("ancestral") && species == Species.UNKNOWN) { species = Species.ANCESTRAL_SEQUENCES; } } else { type = DatabaseType.UNKNOWN; } } return new DatabaseInfo(name, alias, species, type, schemaVersion, genebuildVersion); } private final DatabaseInfo info; private List<Integer> speciesIds; private final DatabaseServer server; private DatabaseRegistry databaseRegistry; private Connection connection; /** The logger to use */ private static Logger logger = Logger.getLogger("HealthCheckLogger"); // ----------------------------------------------------------------- /** * Create a new DatabaseRegistryEntry. * * @param server * The database server where this database resides. * @param name * The name of the database. * @param species * The species that this database represents. If null, derive it * from name. * @param type * The type of this database. If null, derive it from name. */ public DatabaseRegistryEntry(DatabaseServer server, String name, Species species, DatabaseType type) { this.server = server; DatabaseInfo info = getInfoFromName(name, species, type); if (info.getType() == DatabaseType.UNKNOWN) { // try and get the info from the database DatabaseInfo dbInfo = null; try { dbInfo = getInfoFromDatabase(server, name); } catch (SQLException e) { logger.warning(e.getMessage()); } if (dbInfo != null) { info = dbInfo; } } this.info = info; } // ----------------------------------------------------------------- /** * @return Database name. */ public final String getName() { return info.getName(); } /** * @return Species. */ public final Species getSpecies() { return info.getSpecies(); } /** * @return Database type (core, est etc) */ public final DatabaseType getType() { return info.getType(); } // ----------------------------------------------------------------- /** * Compares two databases by comparing the names of the species. If they are * the same, then the schema version is used as a secondary sorting * criterion. * * The schema version is converted to an integer so there is numerical * sorting on the schema version. Otherwise 9 would come after 10. * * This is important, because the comparing is used in * ComparePreviousVersionBase from which all the ComparePreviousVersion* * inherit. */ public int compareTo(DatabaseRegistryEntry dbre) { int speciesOrdering = getSpecies().compareTo(dbre.getSpecies()); if (speciesOrdering != 0) { return speciesOrdering; } return new Integer(getSchemaVersion()).compareTo(new Integer(dbre .getSchemaVersion())); // return getName().compareTo(dbre.getName()); } public String getSchemaVersion() { return info.getSchemaVersion(); } public String getGeneBuildVersion() { return info.getGenebuildVersion(); } // ----------------------------------------------------------------- // Return the numeric genebuild version, or -1 if this cannot be deduced // (e.g. from a non-standard database name) public int getNumericGeneBuildVersion() { if (getGeneBuildVersion() == null) { return -1; } String[] bits = getGeneBuildVersion().split("[a-zA-Z]"); return Integer.parseInt(bits[0]); } public DatabaseRegistry getDatabaseRegistry() { return databaseRegistry; } public void setDatabaseRegistry(DatabaseRegistry databaseRegistry) { this.databaseRegistry = databaseRegistry; } /** * Check if the database has multiple species * * @return true if the database contains more than one species */ public boolean isMultiSpecies() { return this.getName().matches(".*_collection_.*"); } /** * Utility method to determine list of species IDs found within a core * database * * @param con * @param species * @param type * @return list of numeric IDs */ public static List<Integer> getSpeciesIds(Connection con, Species species, DatabaseType type) { List<Integer> speciesId = CollectionUtils.createArrayList(); // only generic databases have a coord_system table if (type == null || !type.isGeneric()) { return speciesId; } Statement stmt = null; ResultSet rs = null; try { stmt = con.createStatement(); rs = stmt .executeQuery("SELECT DISTINCT(species_id) FROM meta where species_id is not null"); if (rs != null) { while (rs.next()) { speciesId.add(rs.getInt(1)); } } } catch (SQLException e) { throw new UtilUncheckedException( "Problem obtaining list of species IDs", e); } finally { DBUtils.closeQuietly(rs); DBUtils.closeQuietly(stmt); } return speciesId; } /** * @return information about the server that this database is found on */ public DatabaseServer getDatabaseServer() { return server; } public Connection getConnection() { if ( (connection == null) || !(ConnectionPool.isValidConnection(connection)) ) { try { connection = server.getDatabaseConnection(getName()); } catch (SQLException e) { logger.warning(e.getMessage()); } } return connection; } /** * Test if this entry is equal to another. Comparison is currently only on * database name. * * @param dbre * @return true if names are the same. */ public boolean equals(DatabaseRegistryEntry dbre) { return (dbre.getName().equals(getName())); } public boolean equals(Object o) { if (o == null) { return false; } return (((DatabaseRegistryEntry) o).getName().equals(getName())); } public String getAlias() { return info.getAlias(); } /** * Utility method to determine list of species IDs found within the attached * database * * @return list of numeric IDs */ public List<Integer> getSpeciesIds() { if (speciesIds == null) { speciesIds = getSpeciesIds(getConnection(), getSpecies(), getType()); } return speciesIds; } public String toString() { return getName(); } // ----------------------------------------------------------------- } // DatabaseRegistryEntry