001package fr.ifremer.adagio.core.dao.technical;
002
003/*
004 * #%L
005 * Tutti :: Persistence API
006 * $Id: TuttiEntities.java 1578 2014-02-07 15:31:18Z tchemit $
007 * $HeadURL: http://svn.forge.codelutin.com/svn/tutti/trunk/tutti-persistence/src/main/java/fr/ifremer/tutti/persistence/entities/TuttiEntities.java $
008 * %%
009 * Copyright (C) 2012 Ifremer
010 * %%
011 * This program is free software: you can redistribute it and/or modify
012 * it under the terms of the GNU Affero General Public License as published by
013 * the Free Software Foundation, either version 3 of the License, or
014 * (at your option) any later version.
015 * 
016 * This program is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
019 * GNU General Public License for more details.
020 * 
021 * You should have received a copy of the GNU Affero General Public License
022 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
023 * #L%
024 */
025
026import java.io.File;
027import java.math.BigDecimal;
028import java.math.MathContext;
029import java.sql.Connection;
030import java.sql.DriverManager;
031import java.sql.ResultSet;
032import java.sql.SQLException;
033import java.sql.Statement;
034import java.text.DecimalFormat;
035import java.text.DecimalFormatSymbols;
036import java.util.Properties;
037
038import javax.sql.DataSource;
039
040import org.apache.commons.lang3.StringUtils;
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043import org.hibernate.Query;
044import org.hibernate.Session;
045import org.hibernate.cfg.AvailableSettings;
046import org.hibernate.cfg.Environment;
047import org.springframework.dao.DataAccessResourceFailureException;
048import org.springframework.dao.DataIntegrityViolationException;
049import org.springframework.dao.DataRetrievalFailureException;
050import org.springframework.jdbc.datasource.DataSourceUtils;
051
052import com.google.common.base.Preconditions;
053
054import fr.ifremer.adagio.core.config.AdagioConfiguration;
055
056/**
057 * Usefull method around DAO and entities.
058 *
059 * @author tchemit <chemit@codelutin.com>
060 * @author Benoit Lavenier <benoit.lavenier@e-is.pro>
061 * @since 3.5
062 */
063public class DaoUtils {
064
065    private final static String JDBC_URL_PREFIX_HSQLDB = "jdbc:hsqldb:file:";
066    
067    /** Logger. */
068    private static final Log log = LogFactory.getLog(DaoUtils.class);
069    
070    protected DaoUtils() {
071        // helper class does not instanciate
072    }
073    
074    /**
075     * Create a new hibernate configuration, with all hbm.xml files
076     * for the schema need for app
077     * 
078     * @return the hibernate Configuration
079     */
080    public static Properties getConnectionProperties(String jdbcUrl, String username, String password, String schema, String dialect, String driver) {
081
082        // Building a new configuration
083        Properties p = new Properties();
084
085        // Set driver
086        p.setProperty(Environment.DRIVER, driver);
087
088        // Set hibernate dialect
089        p.setProperty(Environment.DIALECT, dialect);
090
091        // To be able to retrieve connection
092        p.setProperty(Environment.URL, jdbcUrl);
093        p.setProperty(Environment.USER, username);
094        p.setProperty(Environment.PASS, password);
095
096        if (StringUtils.isNotBlank(schema)) {
097            p.setProperty(Environment.DEFAULT_SCHEMA, schema);
098        }
099
100        // Try with synonyms enable
101        p.setProperty(AvailableSettings.ENABLE_SYNONYMS, "true");
102
103        // Pour tester avec le metadata generic (normalement plus long pour Oracle)
104        // cfg.setProperty("hibernatetool.metadatadialect", "org.hibernate.cfg.rveng.dialect.JDBCMetaDataDialect");
105        if (jdbcUrl.startsWith("jdbc:oracle")) {
106            p.setProperty("hibernatetool.metadatadialect", "org.hibernate.cfg.rveng.dialect.OracleMetaDataDialect");
107        }
108
109        return p;
110    }
111
112    public static void closeSilently(Statement statement) {
113        try {
114            if (statement != null && !statement.isClosed()) {
115                statement.close();
116            }
117        } catch (AbstractMethodError e) {
118            try {
119                statement.close();
120            } catch (SQLException e1) {
121            }            
122            if (log.isDebugEnabled()) {
123                log.debug("Fix this linkage error, damned hsqlsb 1.8.0.7:(");
124            }
125        } catch (IllegalAccessError e) {
126            if (log.isDebugEnabled()) {
127                log.debug("Fix this IllegalAccessError error, damned hsqlsb 1.8.0.7:(");
128            }
129        } catch (Exception e) {
130            if (log.isErrorEnabled()) {
131                log.error("Could not close statement, but do not care", e);
132            }
133        }
134    }
135    
136    public static void closeSilently(Connection connection) {
137        try {
138            if (connection != null && !connection.isClosed()) {
139                connection.close();
140            }
141        } catch (Exception e) {
142            if (log.isErrorEnabled()) {
143                log.error("Could not close connection, but do not care", e);
144            }
145        }
146    }
147
148    public static void closeSilently(ResultSet statement) {
149        try {
150            if (statement != null && !statement.isClosed()) {
151
152                statement.close();
153            }
154        } catch (AbstractMethodError e) {
155            try {
156                statement.close();
157            } catch (SQLException e1) {
158            }            
159            if (log.isDebugEnabled()) {
160                log.debug("Fix this linkage error, damned hsqlsb 1.8.0.7:(");
161            }
162        } catch (IllegalAccessError e) {
163            if (log.isDebugEnabled()) {
164                log.debug("Fix this IllegalAccessError error, damned hsqlsb 1.8.0.7:(");
165            }
166        } catch (Exception e) {
167            if (log.isErrorEnabled()) {
168                log.error("Could not close statement, but do not care", e);
169            }
170        }
171    }
172
173    public static void closeSilently(Session session) {
174        try {
175            if (session != null && session.isOpen()) {
176
177                session.close();
178            }
179        } catch (Exception e) {
180            if (log.isErrorEnabled()) {
181                log.error("Could not close session, but do not care", e);
182            }
183        }
184    }
185    
186    public static Connection createConnection(Properties connectionProperties) throws SQLException {
187        return createConnection(
188                connectionProperties.getProperty(Environment.URL),
189                connectionProperties.getProperty(Environment.USER),
190                connectionProperties.getProperty(Environment.PASS)
191        );
192    }
193
194    public static String getUrl(Properties connectionProperties) {
195        return connectionProperties.getProperty(Environment.URL);
196    }
197
198    public static Connection createConnection(String jdbcUrl,
199                                              String user,
200                                              String password) throws SQLException {
201        Connection connection = DriverManager.getConnection(jdbcUrl,
202                                                            user,
203                                                            password);
204        connection.setAutoCommit(false);
205        return connection;
206    }
207
208    public static void fillConnectionProperties(Properties p,
209                                                String url,
210                                                String username,
211                                                String password) {
212        p.put(Environment.URL, url);
213        p.put(Environment.USER, username);
214        p.put(Environment.PASS, password);
215    }
216
217    public static String getJdbcUrl(File directory, String dbName) {
218        String jdbcUrl = JDBC_URL_PREFIX_HSQLDB + directory.getAbsolutePath() + "/" + dbName;
219        jdbcUrl = jdbcUrl.replaceAll("\\\\", "/");
220        return jdbcUrl;
221    }
222    
223    public static boolean isFileDatabase(String jdbcUrl) {
224        Preconditions.checkNotNull(jdbcUrl);
225        return jdbcUrl.startsWith(JDBC_URL_PREFIX_HSQLDB);
226    }
227    
228    /**
229     * Check if connection properties are valid. Try to open a SQL connection, then close it.
230     * If no error occur, the connection is valid.
231     * @param jdbcDriver
232     * @param jdbcUrl
233     * @param user
234     * @param password
235     * @return
236     */
237    public static boolean isValidConnectionProperties(
238            String jdbcDriver,
239            String jdbcUrl,
240            String user,
241            String password) {
242        String driverClassName = jdbcDriver;
243        try {
244            Class<?> driverClass = Class.forName(driverClassName);
245            DriverManager.registerDriver((java.sql.Driver)driverClass.newInstance());
246        } catch (Exception e) {
247            log.error("Could not load JDBC Driver: " + e.getMessage(), e);
248            return false;
249        } 
250        
251        Connection connection = null;
252        try {
253            connection = createConnection(
254                    jdbcUrl,
255                    user,
256                    password);
257            return true;
258        } catch (SQLException e) {
259            log.error("Could not connect to database: " + e.getMessage().trim());
260        } finally {
261            DaoUtils.closeSilently(connection);
262        }
263        return false;
264    }
265
266
267    private static final double EARTH_RADIUS = 6378288.0;
268
269    private static final MathContext MATH_CONTEXT_4_DIGIT = new MathContext(4);
270
271    private static DecimalFormatSymbols symbols;
272
273    private static DecimalFormat decimalFormat;
274
275    public static int computeDistanceInMeters(Float startLatitude,
276                                              Float startLongitude,
277                                              Float endLatitude,
278                                              Float endLongitude) {
279
280        double sLat = startLatitude * Math.PI / 180.0;
281        double sLong = startLongitude * Math.PI / 180.0;
282        double eLat = endLatitude * Math.PI / 180.0;
283        double eLong = endLongitude * Math.PI / 180.0;
284
285        Double d = EARTH_RADIUS *
286                   (Math.PI / 2 - Math.asin(Math.sin(eLat) * Math.sin(sLat)
287                                            + Math.cos(eLong - sLong) * Math.cos(eLat) * Math.cos(sLat)));
288        return d.intValue();
289    }
290
291    public static String getDistanceInMilles(Float distance) {
292        String distanceText;
293        if (distance != null) {
294            Float distanceInMilles = distance / 1852;
295            distanceText = String.format("%.3f", distanceInMilles);
296
297        } else {
298            distanceText = "";
299        }
300        return distanceText;
301    }
302
303    public static float getRoundedLengthStep(float lengthStep, boolean aroundUp) {
304        int intValue = (int) ((lengthStep + (aroundUp ? 0.001f : 0f)) * 10);
305        float result = intValue / 10f;
306        return result;
307    }
308
309    public static DecimalFormatSymbols getDecimalFormatSymbols() {
310        if (symbols == null) {
311            symbols = new DecimalFormatSymbols();
312            symbols.setDecimalSeparator('.');
313            symbols.setGroupingSeparator(' ');
314        }
315        return symbols;
316    }
317
318    public static DecimalFormat getDecimalFormat(int minDecimal, int maxDecimal) {
319        if (decimalFormat == null) {
320            decimalFormat = new DecimalFormat();
321            decimalFormat.setDecimalFormatSymbols(getDecimalFormatSymbols());
322            decimalFormat.setGroupingUsed(false);
323        }
324        decimalFormat.setMinimumFractionDigits(minDecimal);
325        decimalFormat.setMaximumFractionDigits(maxDecimal);
326        return decimalFormat;
327    }
328
329    public static String getWeightStringValue(Float weight) {
330        String textValue;
331        if (weight != null) {
332            DecimalFormat weightDecimalFormat = getDecimalFormat(1, 3);
333            textValue = weightDecimalFormat.format(weight);
334
335        } else {
336            textValue = "";
337        }
338        return textValue;
339    }
340
341    public static <N extends Number> N getValueOrComputedValue(N value, N computedValue) {
342        return value == null ? computedValue : value;
343    }
344
345    public static <N extends Number> Boolean getValueOrComputedValueComputed(N value, N computedValue) {
346        Boolean result;
347        if (value == null) {
348
349            result = computedValue == null ? null : true;
350        } else {
351            result = false;
352        }
353        return result;
354    }
355
356    /**
357     * Round the given value to max 4 digits.
358     *
359     * @param value the float to round.
360     * @return the rounded value
361     * @since 1.0.1
362     */
363    public static float roundKiloGram(float value) {
364        BigDecimal sumB = new BigDecimal(value);
365        float result = sumB.abs(MATH_CONTEXT_4_DIGIT).floatValue();
366        return result;
367    }
368
369    /**
370     * Compare two weights with rounding them to kilograms.
371     *
372     * @param v0 first weight to compare
373     * @param v1 second weight to compare
374     * @return 1 if v0 > v1, -1 if v0 < v1, 0 if v0 == v1
375     */
376    public static int compareWeights(float v0, float v1) {
377        v0 = roundKiloGram(v0);
378        v1 = roundKiloGram(v1);
379        float delta = v0 - v1;
380        int result;
381        if (delta > 0.00001) {
382            // v0 > v1
383            result = 1;
384        } else if (delta < -0.0001f) {
385            // v0 < v1
386            result = -1;
387        } else {
388            // v0 == v1
389            result = 0;
390        }
391        return result;
392    }
393
394    public static boolean isSmallerWeight(float v0, float v1) {
395        return compareWeights(v0, v1) < 0;
396    }
397
398    public static boolean isGreaterWeight(float v0, float v1) {
399        return compareWeights(v0, v1) > 0;
400    }
401
402    public static boolean isEqualWeight(float v0, float v1) {
403        return compareWeights(v0, v1) == 0;
404    }
405
406    public static boolean isNotEqualWeight(float v0, float v1) {
407        return compareWeights(v0, v1) != 0;
408    }
409    
410    /**
411     * set parameter values for a query: 'statusValidCode' and 'statusTemporaryCode'
412     * @param query a query with this parameters inside
413     * @return the given query object
414     */
415    public static Query withStatus(Query query) {
416        query.setString("statusValidCode", AdagioConfiguration.getInstance().getStatusCodeValid());
417        query.setString("statusTemporaryCode",AdagioConfiguration.getInstance().getStatusCodeTemporary());
418        return query;
419    }
420    
421    public static Double convertToDouble(Float floatValue) {
422        if (floatValue == null) return null;
423        // TODO : trouver une meilleur solution (attention ŕ ne pas perdre de précision)
424        return Double.parseDouble(Float.toString(floatValue));
425    }
426    
427    public static Float convertToFloat(Double doubleValue) {
428        if (doubleValue == null) return null;
429        // TODO : trouver une meilleur solution (attention ŕ ne pas perdre de précision)
430        return Float.parseFloat(Double.toString(doubleValue));
431    }
432    
433    public static int sqlUpdate(DataSource dataSource, String sql) {
434        Connection connection = DataSourceUtils.getConnection(dataSource);
435        try {
436            return sqlUpdate(connection, sql);
437        } 
438        finally {
439            DataSourceUtils.releaseConnection(connection, dataSource);
440        }
441    }
442    
443    public static int sqlUpdate(Connection connection, String sql) {
444        Statement stmt = null;
445        try {
446            stmt = connection.createStatement();
447        } catch (SQLException ex) {
448            closeSilently(stmt);
449            throw new DataAccessResourceFailureException("Could not open database connection", ex);
450        }
451        
452        // Log using a special logger
453        if (log.isDebugEnabled()) {
454            log.debug(sql);
455        }
456        
457        try {
458            return stmt.executeUpdate(sql);
459        } catch (SQLException ex) {
460            throw new DataIntegrityViolationException("Could not execute query: " + sql, ex);
461        }
462        finally {
463            closeSilently(stmt);
464        }
465    }
466    
467    public static Object sqlUnique(DataSource dataSource, String sql) {
468        Connection connection = DataSourceUtils.getConnection(dataSource);
469        try {
470            return sqlUnique(connection, sql);
471        }
472        finally {
473            DataSourceUtils.releaseConnection(connection, dataSource);
474        }
475    }
476    
477    @SuppressWarnings("unchecked")
478    public static <T extends Object> T sqlUniqueTyped(DataSource dataSource, String sql) {
479        return (T)sqlUnique(dataSource, sql);
480    }
481    
482    public static Object sqlUnique(Connection connection, String sql) {
483        Statement stmt = null;
484        try {
485            stmt = connection.createStatement();
486        } catch (SQLException ex) {
487            closeSilently(stmt);
488            throw new DataAccessResourceFailureException("Could not open database connection", ex);
489        }
490        
491        // Log using a special logger
492        if (log.isDebugEnabled()) {
493            log.debug(sql);
494        }
495        
496        try {
497            ResultSet rs = stmt.executeQuery(sql);
498            if (!rs.next()) {
499                throw new DataRetrievalFailureException("Executed query return no row: " + sql);
500            }
501            Object result = rs.getObject(1);
502            if (rs.next()) {
503                throw new DataRetrievalFailureException("Executed query has more than one row: " + sql);
504            }
505            return result;
506            
507        } catch (SQLException ex) {
508            throw new DataIntegrityViolationException("Could not execute query: " + sql, ex);
509        }
510        finally {
511            closeSilently(stmt);
512        }
513    }
514    
515    @SuppressWarnings("unchecked")
516    public static <T extends Object> T sqlUniqueTyped(Connection connection, String sql) {
517        return (T)sqlUnique(connection, sql);
518    }
519    
520    public static void shutdownDatabase(Connection connection) {
521        try {
522            String jdbcUrl = connection.getMetaData().getURL();
523            if (jdbcUrl.startsWith(JDBC_URL_PREFIX_HSQLDB)) {
524                sqlUpdate(connection, "SHUTDOWN");
525            }
526        } catch (SQLException e) {
527            e.printStackTrace();
528        }        
529    }
530    
531    public static void shutdownDatabase(Properties connectionProperties) throws SQLException {
532        Connection conn = DaoUtils.createConnection(connectionProperties);
533        try {
534            shutdownDatabase(conn);
535        } finally {
536            closeSilently(conn);
537        }
538    }
539}
540
541