001/**
002 * Copyright (C) 2010-2015 The Roslin Institute <contact andy.law@roslin.ed.ac.uk>
003 *
004 * This file is part of JEnsembl: a Java API to Ensembl data sources developed by the
005 * Bioinformatics Group at The Roslin Institute, The Royal (Dick) School of
006 * Veterinary Studies, University of Edinburgh.
007 *
008 * Project hosted at: http://jensembl.sourceforge.net
009 *
010 * This is free software: you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License (version 3) as published by
012 * the Free Software Foundation.
013 *
014 * This software is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * in this software distribution. If not, see: http://opensource.org/licenses/gpl-3.0.html
021 */
022package uk.ac.roslin.ensembl.config;
023
024import java.io.Serializable;
025import java.sql.Connection;
026import java.sql.DriverManager;
027import java.sql.SQLException;
028import java.sql.SQLWarning;
029import java.util.*;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032import uk.ac.roslin.ensembl.exception.ConfigurationException;
033
034public class DBConnection {
035
036    protected String newline = (System.getProperty("line.separator") != null) ? System.getProperty("line.separator") : "\r\n";
037    String datasourceStyle = "ensembldb";
038    String driver = null;
039    String url = null;
040    String username = "anonymous";
041    String password = null;
042    String[] invalidDBMatches = null;
043    String[] invalidDBContains = null;
044    String[] invalidDBStart = null;
045    String[] invalidDBEnd = null;
046    DataSource configuredDatasource;
047    Properties dbProperties;
048    protected HashMap<String, String> renamedDBs = new HashMap<String, String>();
049    
050    final static Logger LOGGER = LoggerFactory.getLogger(DBConnection.class);
051    
052    public static enum DataSource implements Serializable {
053
054        ENSEMBLDB("EnsemblDB"),
055        ENSEMBLDB_ARCHIVES("EnsemblDB Archives"),
056        ENSEMBLGENOMES("EnsemblGenomes"),
057        ENSEMBLBACTERIA("EnsemblBacteria"),
058        LOCAL("Local Data Source");
059        private String label;
060        
061        DataSource(String label) {
062            this.label = label;
063        }
064  
065        @Override
066        public String toString() {
067            return label;
068    }
069    }
070
071    public static DataSource getDataSource(String value) {
072        
073        if (value==null) {
074            return null;
075        }
076        if (value.equals("EnsemblDB")) {
077            return DataSource.ENSEMBLDB;
078        }
079        else if (value.equals("EnsemblGenomes")) {
080            return DataSource.ENSEMBLGENOMES;
081        }
082        else if (value.equals("EnsemblBacteria")) {
083            return DataSource.ENSEMBLBACTERIA;
084        }
085        else if (value.equals("EnsemblDB Archives")) {
086            return DataSource.ENSEMBLDB_ARCHIVES;
087        }
088        else if (value.equals("Local Data Source")) {
089            return DataSource.LOCAL;
090        } else {
091            return null;
092        }
093        
094    }
095    
096    public DBConnection(DataSource datasource) throws ConfigurationException {
097        configuredDatasource = datasource;
098        if (datasource == DataSource.ENSEMBLDB) {
099            dbProperties = this.readResource("uk.ac.roslin.ensembl.configfiles.ensembldb");
100        } else if (datasource == DataSource.ENSEMBLDB_ARCHIVES) {
101            dbProperties = this.readResource("uk.ac.roslin.ensembl.configfiles.ensembldb_archives");
102        } else if (datasource == DataSource.ENSEMBLGENOMES || datasource == DataSource.ENSEMBLBACTERIA) {
103            dbProperties = this.readResource("uk.ac.roslin.ensembl.configfiles.ensemblgenomes");
104        } else {
105            throw new ConfigurationException("Unrecognized type of DataSource specified for DBConnection");
106        }
107        initialize();
108    }
109
110    public Properties getConfigurationProperties() {
111        return dbProperties;
112    }
113
114    public DataSource getConfiguredDatasource() {
115        return configuredDatasource;
116    }
117        
118    public DBConnection(DataSource datasource, Properties local_datasource) throws ConfigurationException {
119        if (datasource != DataSource.LOCAL || local_datasource == null) {
120            throw new ConfigurationException("Invalid attempt to configure DBConnection with unrecognized local DataSource.");
121        }
122        configuredDatasource = datasource;
123        dbProperties = local_datasource;
124        initialize();
125    }
126
127    private Properties readResource(String id) throws ConfigurationException {
128        Properties p = null;
129        try {
130            //NB: need to pass in the classloader to get this to work in a test environment!
131            ResourceBundle rb = ResourceBundle.getBundle(id, Locale.getDefault(),
132                this.getClass().getClassLoader());
133            p = new Properties();
134            for (Enumeration keys = rb.getKeys(); keys.hasMoreElements();) {
135                final String key = (String) keys.nextElement();
136                final String value = rb.getString(key);
137                p.put(key, value);
138            }
139        } catch (Exception ex) {
140            throw new ConfigurationException("System can't read the configuration file: " + id);
141        }
142        return p;
143    }
144
145    private void initialize() {
146
147        //at v68 this was changed to iterate through the properties rather than get them by name
148        //this was necessary to allow us to discover dbrename pairs
149        for (String s: dbProperties.stringPropertyNames()) {
150
151            if ( s.equalsIgnoreCase("invalidDBStartsWith") ) {
152                invalidDBStart = dbProperties.getProperty(s).split(" ");
153            }
154            else if (s.equalsIgnoreCase("invalidDBEndsWith") ) {
155                invalidDBEnd = dbProperties.getProperty(s).split(" ");
156            }
157            else if (s.equalsIgnoreCase("invalidDBContains") ) {
158                invalidDBContains = dbProperties.getProperty(s).split(" ");
159            }
160            else if (s.equalsIgnoreCase("invalidDBMatches") ) {
161                invalidDBMatches = dbProperties.getProperty(s).split(" ");
162            }
163            
164            else if (s.equalsIgnoreCase("datasource") ) {
165                datasourceStyle = dbProperties.getProperty(s).trim();
166            }
167            else if (s.equalsIgnoreCase("url")) {
168                url = dbProperties.getProperty(s).trim();
169            }
170            else if (s.equalsIgnoreCase("driver")) {
171                driver = dbProperties.getProperty(s).trim();
172            }
173            else if (s.equalsIgnoreCase("username") ) {
174                username = dbProperties.getProperty(s).trim();
175            }
176            else if (s.equalsIgnoreCase("password") ) {
177                password = dbProperties.getProperty(s).trim();
178            }
179            //parse the DB renames into a HashSet
180            else if (s.startsWith("rename|") ) {
181                String value = dbProperties.getProperty(s).trim();
182                this.renamedDBs.put(s.replace("rename|", ""), value);
183            }    
184        }
185        
186        
187        if (LOGGER.isDebugEnabled()) {
188            if (this.invalidDBContains != null) {
189                LOGGER.debug("skipping databases containing String: " + Arrays.toString(this.invalidDBContains));
190            }
191            if (this.invalidDBMatches != null) {
192                LOGGER.debug("skipping databases matching String: " + Arrays.toString(this.invalidDBMatches));
193            }
194            if (this.invalidDBStart != null) {
195                LOGGER.debug("skipping databases beginning with String: " + Arrays.toString(this.invalidDBStart));
196            }
197            if (this.invalidDBEnd != null) {
198                LOGGER.debug("skipping databases ending with String: " + Arrays.toString(this.invalidDBEnd));
199            }
200
201            LOGGER.debug("datasource: " + datasourceStyle);
202            LOGGER.debug("driver: " + driver);
203            LOGGER.debug("url: " + url);
204            LOGGER.debug("username: " + username);
205            LOGGER.debug("password: " + password);
206            for (Map.Entry<String, String> entry : this.renamedDBs.entrySet()) {
207                LOGGER.debug("renaming database: "+entry.getKey()+" : "+entry.getValue());
208            }
209        }
210    }
211
212    public Boolean isDBNameValid(String s) {
213       if (this.configuredDatasource.equals(DataSource.ENSEMBLBACTERIA) && 
214                !(s.contains("bacteria") || s.contains("collection") || s.contains("pan_homology")) ) {
215            return false;
216        }
217        if (!this.configuredDatasource.equals(DataSource.ENSEMBLBACTERIA) && 
218                (s.contains("bacteria") || s.contains("collection"))) {
219            return false;
220        }        
221        if (this.invalidDBContains != null && this.invalidDBContains.length > 0) {
222            for (int i = 0; i < this.invalidDBContains.length; i++) {
223                if (s.contains(this.invalidDBContains[i])) {
224                    return false;
225                }
226            }
227        }
228        if (this.invalidDBStart != null && this.invalidDBStart.length > 0) {
229            for (int i = 0; i < this.invalidDBStart.length; i++) {
230                if (s.startsWith(this.invalidDBStart[i])) {
231                    return false;
232                }
233            }
234        }
235        if (this.invalidDBEnd != null && this.invalidDBEnd.length > 0) {
236            for (int i = 0; i < this.invalidDBEnd.length; i++) {
237                if (s.endsWith(this.invalidDBEnd[i])) {
238                    return false;
239                }
240            }
241        }
242        if (this.invalidDBMatches != null && this.invalidDBMatches.length > 0) {
243            for (int i = 0; i < this.invalidDBMatches.length; i++) {
244                if (s.equalsIgnoreCase(this.invalidDBMatches[i])) {
245                    return false;
246                }
247            }
248        }
249        return true;
250    }
251
252    public String getDatasourceStyle() {
253        return datasourceStyle;
254    }
255
256    public String report() {
257        String out = newline + "DB CONNECTION REPORT" + newline +"--------------------"+newline+newline;
258
259        out = out + "Datasource Configured: " + configuredDatasource.toString() + newline;
260        out = out + "Datasource style: " + datasourceStyle + newline;
261        out = out + "Datasource URL: " + url + newline;
262        out = out + "Datasource user: " + username + newline;
263
264        if (this.invalidDBContains != null) {
265            out = out.concat("Skipping databases containing String: " + Arrays.toString(this.invalidDBContains)+newline);
266        }
267        if (this.invalidDBMatches != null) {
268            out = out.concat("Skipping databases matching String: " + Arrays.toString(this.invalidDBMatches)+newline);
269        }
270        if (this.invalidDBStart != null) {
271            out = out.concat("Skipping databases beginning with String: " + Arrays.toString(this.invalidDBStart)+newline);
272        }
273        if (this.invalidDBEnd != null) {
274            out = out.concat("Skipping databases ending with String: " + Arrays.toString(this.invalidDBEnd)+newline);
275        }
276
277        out = out+newline;
278        return out;
279    }
280
281    public String testConnection() {
282        String warning = "";
283
284        try {
285            Class.forName("com.mysql.jdbc.Driver");
286//            if (LOGGER.isDebugEnabled()) {
287//                PrintWriter w = new PrintWriter(System.err);
288//                DriverManager.setLogWriter(w);
289//            }
290            Connection conn = DriverManager.getConnection(url, username, password);
291
292            SQLWarning warn = conn.getWarnings();
293
294            if (warn != null) {
295                warning = "WARNING:\n";
296            }
297
298            while (warn != null) {
299                warning += "SQLState: " + warn.getSQLState() + "\n";
300                warning += "Message:  " + warn.getMessage() + "\n";
301                warning += "Vendor:   " + warn.getErrorCode() + "\n";
302                warn = warn.getNextWarning();
303            }
304
305
306            conn.close();
307
308        } catch (ClassNotFoundException e) {
309            warning += "ERROR: Can't load driver\n" + e;
310        } catch (SQLException e) {
311            warning += "ERROR: Database access failed\n" + e;
312    }
313        if (warning.isEmpty()) {
314            return "OK";
315        } else {
316            return warning;
317        }
318
319    }
320
321    public boolean isConnectionOK() {
322
323        try {
324            Class.forName("com.mysql.jdbc.Driver");
325//            if (LOGGER.isDebugEnabled()) {
326//                PrintWriter w = new PrintWriter(System.err);
327//                DriverManager.setLogWriter(w);
328//            }
329            Connection conn = DriverManager.getConnection(url, username, password);
330
331            SQLWarning warn = conn.getWarnings();
332
333            if (warn != null) {
334                return false;
335            }
336            conn.close();
337        } catch (Exception e) {
338            return false;
339        }
340
341        return true;
342
343    }
344
345    public HashMap<String, String> getRenamedDBs() {
346        return renamedDBs;
347    }
348}