/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.utils.sql.vendor.oracle;

import com.queplix.core.error.GenericSystemException;
import oracle.jdbc.driver.OracleCallableStatement;
import oracle.jdbc.driver.OraclePreparedStatement;
import oracle.jdbc.driver.OracleResultSet;
import oracle.sql.BLOB;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

/**
 * This is a convenience class meant to generalize the action of updating an
 * BLOB column in an Oracle database. This class MUST be thread-safe.
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:31:23 $
 */
public class OracleBlob
        implements Serializable {

    // =============================================================== Constants

    // Stored procedures call strings.
    protected final static String GET_EMPTY_BLOB_PROC
            = "{CALL GET_EMPTY_BLOB(?)}";
    protected final static String FREE_TMP_BLOB_PROC
            = "{CALL FREE_TMP_BLOB(?)}";

    // ========================================================== Public methods

    /**
     * Sets the BLOB into the PreparedStement object.
     *
     * @param stat PreparedStement object to set BLOB into
     * @param pos  substitution position in statement
     * @param data BLOB to set
     */
    public void setObject(PreparedStatement stat, int pos, byte[] data) {

        BLOB blob = null;
        Connection conn = null;

        try {
            conn = stat.getConnection();
            if(data != null) {
                blob = createBLOB(conn, data);
                ((OraclePreparedStatement) stat).setBLOB(pos, blob);
            } else {
                stat.setNull(pos, Types.BLOB);
            }

        } catch (GenericSystemException e) {
            throw e;

        } catch (Exception e) {
            freeBLOB(conn, blob);
            throw new GenericSystemException("Can`t set object", e);
        }

    }

    /**
     * Retrives the BLOB object from ResultSet object.
     *
     * @param rs  ResultSet object to retrieve the BLOB from
     * @param pos substitution position in statement
     * @return BLOB data
     */
    public byte[] getObject(ResultSet rs, int pos) {

        BLOB blob = null;
        try {
            blob = ((OracleResultSet) rs).getBLOB(pos);
        } catch (Exception e) {
            throw new GenericSystemException("Can't get object", e);
        }

        if(blob == null) {
            return null;
        } else {
            return retriveObject(blob);
        }
    }

    /**
     * Retrives the BLOB object from CallableStatement object.
     *
     * @param cs  CallableStatement object to retrieve the BLOB from
     * @param pos substitution position in statement
     * @return BLOB data
     */
    public byte[] getObject(CallableStatement cs, int pos) {

        BLOB blob = null;

        try {
            blob = ((OracleCallableStatement) cs).getBLOB(pos);
        } catch (Exception e) {
            throw new GenericSystemException("Can't get object", e);
        }
        if(blob == null) {
            return null;
        } else {
            return retriveObject(blob);
        }
    }

    // ========================================================= Protected methods

    // Retrives the BLOB object from ResultSet.

    protected byte[] retriveObject(BLOB blob) {

        InputStream is = null;
        ByteArrayOutputStream baos = null;
        byte[] data = null;

        try {
            is = blob.getBinaryStream();
            int len = blob.getBufferSize();
            byte[] buffer = new byte[len];

            baos = new ByteArrayOutputStream();
            int i = 0;
            while((i = is.read(buffer)) != -1) {
                baos.write(buffer, 0, i);
            }
            baos.flush();
            is.close();

            data = baos.toByteArray();

        } catch (Exception e) {
            throw new GenericSystemException("Can't get object", e);

        } finally {
            try {
                if(baos != null) {
                    baos.close();
                }
            } catch (IOException ex) {
            }
            try {
                if(is != null) {
                    is.close();
                }
            } catch (IOException ex) {
            }
        }

        return data;

    }

    // Creates or update a BLOB with data from the given object.
    protected BLOB createBLOB(Connection conn, byte[] data) {

        BLOB blob = null;
        CallableStatement cs = null;

        try {

            // Get an empty BLOB locator.
            cs = conn.prepareCall(GET_EMPTY_BLOB_PROC);
            cs.registerOutParameter(1, Types.BLOB);
            cs.execute();

            blob = ((OracleCallableStatement) cs).getBLOB(1);
            blob.putBytes(1, data);

        } catch (Exception e) {
            e.printStackTrace();
            freeBLOB(conn, blob);
            throw new GenericSystemException("Can`t create a BLOB.", e);

        } finally {
            try {
                if(cs != null) {
                    cs.close();
                }
            } catch (SQLException ex) {
            }
        }

        return blob;

    }

    // Frees the BLOB when an exception is raised.
    protected void freeBLOB(Connection conn, BLOB blob) {

        PreparedStatement ps = null;
        try {
            if(conn != null && blob != null) {
                ps = conn.prepareStatement(FREE_TMP_BLOB_PROC);
                ((OraclePreparedStatement) ps).setBLOB(1, blob);
                ps.execute();
            }

        } catch (Exception ex) {
            // .. do nothing

        } finally {
            try {
                if(ps != null) {
                    ps.close();
                }
            } catch (SQLException ex) {
            }
        }
    }

}
