/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/impl/rdbms/CommonRDBMSAdapter.java,v 1.13 2005/05/19 13:01:32 unico Exp $
 * $Revision: 1.13 $
 * $Date: 2005/05/19 13:01:32 $
 *
 * ====================================================================
 *
 * Copyright 2004-2005 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.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.slide.common.Service;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.security.NodePermission;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.util.logger.Logger;


/**
 * This adapter has code that has been ported from OracleRDBMSAdapter so that it can be
 * resued by both OracleRDBMSAdapter and DB2RDBMSAdapter.
 *
 */
public class CommonRDBMSAdapter extends StandardRDBMSAdapter {

    // Constructor

    public CommonRDBMSAdapter(Service service, Logger logger) {
        super(service, logger);
    }



    public void removeObject(Connection connection, Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectNotFoundException
    {
        PreparedStatement statement = null;
        try {
            clearBinding(connection, uri);

            // delete links
            try {
                statement =
                    connection.prepareStatement(
                        "delete from LINKS l where l.URI_ID in (" +
                        "select u.URI_ID from URI u where u.URI_STRING = ?)");
                statement.setString(1, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
            // delete version history
            // FIXME: Is this true??? Should the version history be removed if the object is removed???
            try {
                statement =
                    connection.prepareStatement(
                        "delete from VERSION_HISTORY vh where vh.URI_ID in (" +
                        "select u.URI_ID from URI u where u.URI_STRING = ?)");
                statement.setString(1, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
            // delete version
            try {
                statement =
                    connection.prepareStatement(
                        "delete from VERSION v where v.URI_ID in (" +
                        "select u.URI_ID from URI u where u.URI_STRING = ?)");
                statement.setString(1, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
            // delete the object itself
            try {
                statement =
                    connection.prepareStatement(
                        "delete from OBJECT o where o.URI_ID in (" +
                        "select u.URI_ID from URI u where u.URI_STRING = ?)");
                statement.setString(1, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
            // finally delete the uri
            try {
                statement = connection.prepareStatement("delete from URI where URI_STRING = ?");
                statement.setString(1, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
        } catch (SQLException e) {
            throw createException(e, uri.toString());

        }
    }

    public void removeRevisionContent(Connection connection, Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {
        try {
            PreparedStatement statement = null;
            try {
                statement =
                    connection.prepareStatement(
                        "delete from VERSION_CONTENT vc where vc.VERSION_ID in (" +
                        "select vh.VERSION_ID from VERSION_HISTORY vh, URI u where vh.REVISION_NO = ? and vh.URI_ID=u.URI_ID AND u.URI_STRING=?)");
                statement.setString(1, revisionDescriptor.getRevisionNumber().toString());
                statement.setString(2, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
        } catch (SQLException e) {
            throw createException(e, uri.toString());
        }
    }

    public void removeRevisionDescriptors(Connection connection, Uri uri) throws ServiceAccessException {
        PreparedStatement statement = null;
        try {
            statement =
                connection.prepareStatement(
                "delete from VERSION_PREDS vp where vp.VERSION_ID in (" +
                "select vh.VERSION_ID from VERSION_HISTORY vh, URI u where vh.URI_ID = u.URI_ID and u.URI_STRING = ?)");
            statement.setString(1, uri.toString());
            statement.executeUpdate();
        } catch (SQLException e) {
            throw createException(e, uri.toString());
        } finally {
            close(statement);
        }
    }

    public void removeRevisionDescriptor(Connection connection, Uri uri, NodeRevisionNumber revisionNumber)
        throws ServiceAccessException
    {
        PreparedStatement statement = null;
        try {
            try {
                statement =
                    connection.prepareStatement(
                        "delete from VERSION_LABELS vl where vl.VERSION_ID in (" +
                        "select vh.VERSION_ID from VERSION_HISTORY vh, URI u where vh.REVISION_NO = ? and vh.URI_ID = u.URI_ID AND u.URI_STRING = ?)");
                statement.setString(1, revisionNumber.toString());
                statement.setString(2, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
            try {
                statement =
                    connection.prepareStatement(
                        "delete from PROPERTIES p where p.VERSION_ID in (" +
                        "select vh.VERSION_ID from VERSION_HISTORY vh, URI u where vh.REVISION_NO = ? and vh.URI_ID = u.URI_ID AND u.URI_STRING = ?)");
                statement.setString(1, revisionNumber.toString());
                statement.setString(2, uri.toString());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
        } catch (SQLException e) {
            throw createException(e, uri.toString());
        }
    }

    public void removeLock(Connection connection, Uri uri, NodeLock lock)
        throws ServiceAccessException, LockTokenNotFoundException {
        PreparedStatement statement = null;
        try {
            // FIXME: What about inheritage?
            try {
                statement =
                    connection.prepareStatement(
                        "delete from LOCKS where LOCKS.LOCK_ID in (select u.URI_ID from URI u where u.URI_STRING=?)");
                statement.setString(1, lock.getLockId());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
            try {
                statement =
                    connection.prepareStatement(
                        "delete from URI where URI_STRING=?");
                statement.setString(1, lock.getLockId());
                statement.executeUpdate();
            } finally {
                close(statement);
            }
        } catch (SQLException e) {
            throw createException(e, uri.toString());
        }
    }

    public void revokePermission(Connection connection, Uri uri, NodePermission permission)
        throws ServiceAccessException {
        if (permission == null) return;
        StringBuffer sql = new StringBuffer("delete from PERMISSIONS where (OBJECT_ID, SUBJECT_ID, ACTION_ID) IN" +
                " (SELECT ou.URI_ID, su.URI_ID, au.URI_ID FROM URI ou, URI su, URI au WHERE ou.URI_STRING = ? and su.URI_STRING = ? and au.URI_STRING = ?)");
        PreparedStatement statement = null;
        try {
            final NodeRevisionNumber revisionNumber;
            revisionNumber = permission.getRevisionNumber();
            // generate proper sql based on content of revision number
            if (revisionNumber != null) {
            	sql.append(" and VERSION_NO = ?");
            } else {
            	sql.append(" and VERSION_NO IS NULL");
            }
            statement = connection.prepareStatement(sql.toString());
            statement.setString(1, uri.toString());
            statement.setString(2, permission.getSubjectUri());
            statement.setString(3, permission.getActionUri());
            if (revisionNumber != null) {
                statement.setString(4, revisionNumber.toString());
            }
            statement.executeUpdate();
        } catch (SQLException e) {
            throw createException(e, uri.toString());
        } finally {
            close(statement);
        }
    }

    public void revokePermissions(Connection connection, Uri uri) throws ServiceAccessException {
        PreparedStatement statement = null;
        try {
            statement =
                connection.prepareStatement(
                    "delete from PERMISSIONS where PERMISSIONS.OBJECT_ID in (select u.URI_ID from URI u where u.URI_STRING = ?)");
            statement.setString(1, uri.toString());
            statement.executeUpdate();
        } catch (SQLException e) {
            throw createException(e, uri.toString());
        } finally {
            close(statement);
        }
    }

    protected void clearBinding(Connection connection, Uri uri)
        throws ServiceAccessException, ObjectNotFoundException, SQLException
    {
        PreparedStatement statement = null;

        try {
            statement =
                connection.prepareStatement(
                    "delete from BINDING where BINDING.URI_ID in (select URI_ID from URI where URI.URI_STRING = ?)");
            statement.setString(1, uri.toString());
            statement.executeUpdate();
        } finally {
            close(statement);
        }

        try {
            statement =
                connection.prepareStatement(
                    "delete from PARENT_BINDING where PARENT_BINDING.URI_ID in (select URI_ID from URI where URI.URI_STRING = ?)");
            statement.setString(1, uri.toString());
            statement.executeUpdate();
        } finally {
            close(statement);
        }
    }

    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[2048];
                            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();
            }
                
            PreparedStatement statement = null;
            try {
                long versionID = getVersionID(connection, uri.toString(), revisionDescriptor);
                statement = connection.prepareStatement(
                    "insert into VERSION_CONTENT (VERSION_ID, CONTENT) values (?,?)");
                statement.setLong(1, versionID);
                statement.setBinaryStream(2, is, (int) blobLength);
                statement.executeUpdate();
                if (tempFile != null) {
                    is.close();
                    is = null;
                    tempFile.delete();
                }
            } finally {
                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 long getVersionID(
        Connection connection, 
        String uriString, 
        NodeRevisionDescriptor revisionDescriptor) throws  SQLException 
    {
        PreparedStatement statement = null;
        ResultSet rs = null;
        long versionID = 0l;
        try {        
            statement = connection.prepareStatement(
                "select vh.VERSION_ID from VERSION_HISTORY vh, URI u where vh.URI_ID = u.URI_ID and u.URI_STRING = ? and vh.REVISION_NO = ?");
            statement.setString(1,uriString);
            statement.setString(2,revisionDescriptor.getRevisionNumber().toString());
            rs = statement.executeQuery();
            if (rs.next()) {
                versionID = rs.getLong(1);
            }
        } finally {
            close(statement,rs);
        }
        
        return versionID;
    }

    protected String convertRevisionNumberToComparable(String revisioNumber) {
        return "to_number(substr("+revisioNumber+",1,instr("+revisioNumber+",'.')-1)), to_number(substr("+revisioNumber+",instr("+revisioNumber+",'.')+1))";
    }

}


