/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/impl/rdbms/OracleRDBMSAdapter.java,v 1.12 2004/11/13 15:10:07 dsavazzi Exp $
 * $Revision: 1.12 $
 * $Date: 2004/11/13 15:10:07 $
 *
 * ====================================================================
 *
 * Copyright 1999-2003 The Apache Software Foundation
 *
 * 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 org.apache.slide.store.impl.rdbms;

import java.io.*;
import java.lang.reflect.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.slide.common.Uri;
import org.apache.slide.common.Service;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.util.logger.Logger;


/**
 * Adapter for Oracle 9 and 10.
 *
 * @version $Revision: 1.12 $
 */
public class OracleRDBMSAdapter extends CommonRDBMSAdapter implements SequenceAdapter {

    // Constructor

    public OracleRDBMSAdapter(Service service, Logger logger) throws ClassNotFoundException, NoSuchMethodException 
    {
        super(service, logger);
        
        Class bCls = Class.forName("oracle.sql.BLOB");
        blobGetOutStreamMethod = bCls.getMethod("getBinaryOutputStream", new Class[] {});
    }


    // Public Methods

    protected void storeContent(
        Connection connection, Uri uri,
        NodeRevisionDescriptor revisionDescriptor,
        NodeRevisionContent revisionContent) throws IOException, SQLException 
    {
        assureVersionInfo(connection, uri, revisionDescriptor);
        
        InputStream is = revisionContent.streamContent();
        if (is != null) {
            long blobLength = 0;
            File tempFile = null;
            
            if (bcompress) {
                getLogger().log("Compressing the data", LOG_CHANNEL,
                Logger.DEBUG);
                StoreContentZip ziputil = new StoreContentZip();
                ziputil.Zip(is);
                is = ziputil.getInputStream();
                
                // fix RevisionDescriptor Content Length
                if (revisionDescriptor.getContentLength() == -1) {
                    revisionDescriptor.setContentLength(ziputil.getInitialContentLength());
                }
                
                blobLength = ziputil.getContentLength();
            } else {
                // fix RevisionDescriptor Content Length
                if (revisionDescriptor.getContentLength() == -1) {
                    try {
                        tempFile = File.createTempFile("content", null);
                        FileOutputStream fos = new FileOutputStream(tempFile);
                        try {
                            byte buffer[] = new byte[4096];
                            do {
                                int nChar = is.read(buffer);
                                if (nChar == -1) {
                                    break;
                                }
                                fos.write(buffer, 0, nChar);
                            } while (true);
                        } finally {
                            fos.close();
                        }
                        is.close();
                        is = new FileInputStream(tempFile);
                        
                        revisionDescriptor.setContentLength(tempFile.length());
                        
                    } catch (IOException ex) {
                        getLogger().log(ex.toString() + " during the calculation of the content length.", 
                            LOG_CHANNEL, Logger.ERROR);
                        throw ex;
                    }
                }
                
                blobLength = revisionDescriptor.getContentLength();
            }
            
            CallableStatement statement = null;
            try {
                long versionID = getVersionID(connection, uri.toString(), revisionDescriptor);
                statement = connection.prepareCall(
                    "BEGIN "
                    + "insert into VERSION_CONTENT (VERSION_ID, CONTENT) "
                    + "values (?, empty_blob()) RETURN CONTENT into ?;"
                    + "END; "
                );
                statement.setLong(1, versionID);
                statement.registerOutParameter(2, java.sql.Types.BLOB);
                
                statement.executeUpdate();
                
                // do these two lines using reflection
                // BLOB blob = (BLOB) statement.getBlob(2);
                // OutputStream os = blob.getBinaryOutputStream();
                
                Object bObj = statement.getBlob(2);
                OutputStream os = null;
                try {
                    os = (OutputStream) blobGetOutStreamMethod.invoke(bObj, new Object[] {});
                } catch (Exception ex) {
                    ex.printStackTrace();
                    throw new IOException("Reflection error");
                }
                
                try {
                    byte buffer[] = new byte[4096];
                    do {
                        int nChar = is.read(buffer);
                        if (nChar == -1) {
                            break;
                        }
                        os.write(buffer, 0, nChar);
                    } while (true);
                } finally {
                    os.flush();
                    os.close();
                }
                
            } finally {
                if (tempFile != null) {
                    try {
                        is.close();
                    } catch (Exception ex) {
                        // ignore
                    }
                    is = null;
                    if (!tempFile.delete()) {
                        logger.log("Could not delete file \"" + tempFile.getAbsolutePath() + 
                            "\"");
                    }
                }
                
                try {
                    close(statement);
                } finally {
                    if (is != null) {
                        // XXX some JDBC drivers seem to close the stream upon
                        // closing of
                        // the statement; if so this will raise an IOException
                        // silently ignore it...
                        try {
                            is.close();
                        } catch (IOException ioe) {
                            logger.log("Could not close stream", ioe, LOG_CHANNEL, Logger.DEBUG);
                        }
                    }
                }
            }
        }
    }

    protected static String normalizeSequenceName(String sequenceName) {
        return sequenceName.replace('-', '_').toUpperCase() + "_SEQ";
    }
    
    public boolean isSequenceSupported(Connection conn) {
        return true;
    }

    public boolean createSequence(Connection conn, String sequenceName) throws ServiceAccessException {

        String query = "CREATE SEQUENCE \"" + normalizeSequenceName(sequenceName) + "\"";

        PreparedStatement statement = null;

        try {
            statement = conn.prepareStatement(query);
            statement.executeUpdate();
            return true;
        } catch (SQLException e) {
            throw new ServiceAccessException(service, e);
        } finally {
            close(statement);
        }

    }

    public long nextSequenceValue(Connection conn, String sequenceName) throws ServiceAccessException {
        String selectQuery = "SELECT \"" + normalizeSequenceName(sequenceName)+"\".nextval FROM DUAL";

        PreparedStatement selectStatement = null;
        ResultSet res = null;

        try {
            selectStatement = conn.prepareStatement(selectQuery);
            res = selectStatement.executeQuery();
            if (!res.next()) {
                throw new ServiceAccessException(service, "Could not increment sequence " + sequenceName);
            }
            long value = res.getLong(1);
            return value;
        } catch (SQLException e) {
            throw new ServiceAccessException(service, e);
        } finally {
            close(selectStatement, res);
        }
    }

    public boolean sequenceExists(Connection conn, String sequenceName) throws ServiceAccessException {

        PreparedStatement selectStatement = null;
        ResultSet res = null;

        try {
            selectStatement =
                conn.prepareStatement("ALTER SEQUENCE  \"" + normalizeSequenceName(sequenceName) + "\" INCREMENT BY 1");
            res = selectStatement.executeQuery();
            return true;
        } catch (SQLException e) {
            return false;
        } finally {
            close(selectStatement, res);
        }
    }

    
    // Attributes
    
    protected Method blobGetOutStreamMethod;
}


