All Downloads are FREE. Search and download functionalities are using the official Maven repository.

redora.service.ServiceBase Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2010 Nanjing RedOrange ltd (http://www.red-orange.cn)
 *
 * 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.
 */
package redora.service;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import redora.api.fetch.Mode;
import redora.api.fetch.Page;
import redora.db.DatabaseFactory;
import redora.db.Statement;
import redora.exceptions.ConnectException;
import redora.exceptions.PagingException;
import redora.exceptions.PersistException;
import redora.exceptions.QueryException;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Logger;

import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Level.INFO;
import static redora.db.DatabaseFactory.getDBProperty;

/**
 * Base class for the java service entry classes providing the database
 * connection management. This class will cache all prepared statements and it
 * will maintain a database connection. With the makePreparedStatement function
 * a hash in the SQL query determines if a prepared statement is already in the
 * cache.
 *
 * @author Nanjing RedOrange (http://www.red-orange.cn)
 */
public abstract class ServiceBase {

    private static final transient Logger l = Logger.getLogger("redora.ServiceBase");

    protected Statement st;
    boolean closeStatement = true;
    boolean closed = false;
    final String schema;
    /** If true, the system will return to auto commit when an transaction is ended.*/
    public boolean defaultAutoCommit;

    /** true if you are in a Transaction @see #beginTransaction */
    public boolean inTransaction = false;

    /** Last time in System.currentTime when this service was invoked. */
    public long last;

    protected ServiceBase(@NotNull String schema) throws ConnectException {
        this.schema = schema;
        st = DatabaseFactory.statement(schema);
        last = System.currentTimeMillis();
        defaultAutoCommit = "true".equals(getDBProperty(schema, "autoCommit"));
    }

    public ServiceBase(@NotNull final ServiceBase chain, @NotNull String schema) {
        this.schema = schema;
        st = chain.st;
        closeStatement = false; //chain master handles this
        defaultAutoCommit = false; //By setting this to false, this service will not mangle with the transaction settings
    }

    public void beginTransaction() throws PersistException {
        inTransaction = true;
        last = System.currentTimeMillis();
        try {
            if (defaultAutoCommit) {
                st.con.con.setAutoCommit(false);
            }
        } catch (SQLException e) {
            reset(l);
            throw new PersistException("Failed to start transaction", e);
        }
    }

    public void commit() throws PersistException {
        inTransaction = false;
        last = System.currentTimeMillis();
        try {
            st.con.con.commit();
            if (defaultAutoCommit) {
                st.con.con.setAutoCommit(true);
            }
        } catch (SQLException e) {
            reset(l);
            throw new PersistException("Failed to commit transaction", e);
        }
    }

    public void rollback() throws PersistException {
        inTransaction = false;
        last = System.currentTimeMillis();
        try {
            st.con.con.rollback();
            if (defaultAutoCommit) {
                st.con.con.setAutoCommit(true);
            }
        } catch (SQLException e) {
            reset(l);
            throw new PersistException("Failed to rollback transaction", e);
        }
    }

    /**
     * A 'final' resort method that closes the statement and connection
     * and re-initializes it. Use reset in the catch blocks where you handle
     * exceptions that should not occur. Using reset(0 in these cases makes
     * the application more robust.
     */
    public void reset(@NotNull final Logger parent) {
        l.log(INFO, "Attempting to reset the connection, from {0}", parent.getName());
        inTransaction = false;
        last = System.currentTimeMillis();
        if (closeStatement) {
            st.close();
            try {
                st = DatabaseFactory.statement(schema);
            } catch (ConnectException e) {
                l.log(SEVERE, "Failed to reset", e);
            }
        }
    }

    /**
     * Normally you do not need this use ServiceFactory.close(Service) instead.
     * This method will release any open statement and connection related to this
     * service.
     */
    public void close() {
        inTransaction = false;
        if (closeStatement) {
            st.close();
        }
        closed = true;
    }

    @Override
    public void finalize() throws Throwable {
        if (!closed && closeStatement) {
            l.log(INFO, "You might want to close services yourself, i will do it now for you");
            close();
        }
        super.finalize();
    }

    /**
     * Adds paging info to the given SQL. This means for example the statement
     * 'select * from myTable' will be enhanced to something like:
     * 'select * from myTable limit 20, 30'
     * 
* It will only add it when Page has Mode.Page. If a result size was not established * it will use given SQL to count the number of records. So use this after all * the query parameters are set. * * @param sql (Mandatory) The SQL to which you want to add the LIMIT statement * @param page (Mandatory) The Page object managing the paging. * @throws PagingException This method can run a count query on the DB, when that fails this is the exception. * @return An SQL statement suffixed with limit x,y */ @NotNull public String preparePage(@NotNull final String sql, @NotNull final Page page) throws PagingException { if (page.getMode() == Mode.Page) { if (page.resultCount() == -1) { page.initPageMode(getResultSize(sql)); } return new StringBuilder(sql).append(" limit ").append(page.position() * page.pageSize()) .append(',').append(page.pageSize()).toString(); } return sql; } public void execute(@NotNull final String sql) throws QueryException { last = System.currentTimeMillis(); try { st.st.execute(sql); } catch (SQLException e) { l.log(SEVERE, "Failed to perform query: " + sql, e); reset(l); throw new QueryException("Failed to perform query: " + sql, e); } } public ResultSet sqlQuery(@NotNull final String sql) throws SQLException { last = System.currentTimeMillis(); return st.st.executeQuery(sql); } /** * Looks up the database meta data to determine if the table exists. * * @param tableName (Mandatory) Any table name you wish to see if it exists. * @return True if the table exists. * @throws QueryException When something fails in the query for the existence of tableName. */ public boolean tableExists(@NotNull final String tableName) throws QueryException { last = System.currentTimeMillis(); ResultSet rs = null; try { rs = st.con.con.getMetaData().getTables(null, null, tableName, null); return rs.next(); } catch (SQLException e) { l.log(SEVERE, "Failed to open meta data to find table " + tableName, e); reset(l); throw new QueryException("Failed to open meta data to find table " + tableName, e); } finally { close(rs); } } /** * Retrieves the last attribute name on which the unique key is defined. * * @param tableName (Mandatory) table name * @param keyName (Mandatory) unique index name * @return null, or the attribute name of the index * @throws QueryException When the meta data could not be retrieved. */ @Nullable public String uniqueKeyAttribute(@NotNull final String tableName, @NotNull final String keyName) throws QueryException { last = System.currentTimeMillis(); ResultSet rs = null; try { rs = st.con.con.getMetaData().getIndexInfo(null, null, tableName, true, true); while (rs.next()) { if (keyName.equalsIgnoreCase(rs.getString("INDEX_NAME"))) { return rs.getString("COLUMN_NAME"); //TODO now it gets the last value: maybe it should get the first. } } } catch (SQLException e) { reset(l); throw new QueryException("Cannot retrieve Index: " + keyName, e); } finally { close(rs); } return null; } /** * Get the total size of the query size for the given SQL. * * @param sql (Mandatory) The SQL sentence from which you want to determine the # of records it will produce. * @return The count(1) result of given query. * @throws PagingException When i cannot connect to the DB or the count(1) query fails */ public int getResultSize(@NotNull final String sql) throws PagingException { last = System.currentTimeMillis(); String SQL = sql.replace(sql.substring(sql.indexOf("select") + 7, sql.indexOf("from") - 1), "count(1)"); ResultSet rs = null; try { rs = st.st.executeQuery(SQL); if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { l.log(SEVERE, "Failed to retrieve query size: " + SQL, e); reset(l); throw new PagingException("Failed to retrieve query size: " + SQL, e); } finally { close(rs); } return 0; } /** * Safely closes ResultSet, exceptions will be logged as warning. * @param rs (Optional) the to be closed resultset */ public static void close(@Nullable final ResultSet rs) { try { if (rs != null) { rs.close(); } } catch (SQLException e) { l.log(WARNING, "Failed to close result set", e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy