/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.myfaces.orchestra.connectionManager;

import org.apache.commons.logging.LogFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Create proxy connections able to handle disconnection of the underlying real connection.
 *
 * @see org.apache.myfaces.orchestra.connectionManager.DisconnectableConnection
 */
public class DisconnectableConnectionFactory
{
    private DisconnectableConnectionFactory()
    {
    }

    public static DisconnectableConnection create(final ConnectionManagerDataSource connectionManager)
    {
        return (DisconnectableConnection) Proxy.newProxyInstance(

            DisconnectableConnection.class.getClassLoader(),

            new Class[]
                {
                    DisconnectableConnection.class
                },

            new InvocationHandler()
            {
                private Map connectionConfiguration = new HashMap();
                private Connection connection;

                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
                {
                    if ("equals".equals(method.getName()) || "hashCode".equals(method.getName())) // NON-NLS
                    {
                        // do not pass these methods to the connection as we dont want to change the
                        // identity of the connection proxy
                        return method.invoke(this, args);
                    }
                    else if ("close".equals(method.getName())) // NON-NLS
                    {
                        try
                        {
                            if (connection != null)
                            {
                                connection.close();
                            }
                            connection = null;
                        }
                        finally
                        {
                            connectionManager.onAfterReleaseConnection((Connection) proxy);
                        }
                        return null;
                    }
                    else if ("disconnect".equals(method.getName())) // NON-NLS
                    {
                        try
                        {
                            if (connection != null)
                            {
                                try
                                {
                                    if (!connection.isClosed())
                                    {
                                        connection.close();
                                    }
                                }
                                catch (SQLException e)
                                {
                                    LogFactory.getLog(DisconnectableConnectionFactory.class)
                                        .warn(e.getLocalizedMessage(), e);
                                }
                            }
                        }
                        finally
                        {
                            connectionManager.onAfterReleaseConnection((Connection) proxy);
                            connection = null;
                        }
                        return null;
                    }
                    else if ("getConnection".equals(method.getName())) // NON-NLS
                    {
                        return connection;
                    }

                    if (connection == null)
                    {
                        connection = connectionManager.getDataSource().getConnection();

                        // if we have a configuration, we have to replay it now
                        Iterator iterConfiguration = connectionConfiguration.entrySet().iterator();
                        while (iterConfiguration.hasNext())
                        {
                            Map.Entry config = (Map.Entry) iterConfiguration.next();
                            ((Method) config.getKey()).invoke(connection, (Object[]) config.getValue());
                        }

                        connectionManager.onAfterBorrowConnection((Connection) proxy);
                    }

                    Object ret = method.invoke(connection, args);

                    // capture the connection configuration
                    if (method.getName().startsWith("set")) // NON-NLS
                    {
                        connectionConfiguration.put(method, args);
                    }

                    return ret;
                }
            });
    }
}
