/*
 * 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.jackrabbit.jca;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;

import org.apache.jackrabbit.commons.JcrUtils;

/**
 * Implements the JCA ManagedConnectionFactory contract.
 */
public class JCAManagedConnectionFactory
        implements ManagedConnectionFactory {

	private static final long serialVersionUID = 4775747072955577131L;

	/**
     * Repository parameters.
     */
    private final Map<String, String> parameters = new HashMap<String, String>();

    /**
     * Key for the repository home
     */
    final static String HOMEDIR_KEY = "org.apache.jackrabbit.repository.home";
    /**
     * Key for the repository config file
     */
    final static String CONFIGFILE_KEY = "org.apache.jackrabbit.repository.conf";
    
    /**
     * Flag indicating whether the session should be bound to the
     * transaction lifecycle.
     * In other words, if this flag is true the handle
     * will be closed when the transaction ends.
     */
    private Boolean bindSessionToTransaction = Boolean.TRUE;

    /**
     * Flag indicating whether the Repository should start
     * immediately or lazy on first access. Per default true so the Repository will
     * start immediately if this JCAManagedConnectionFactory will be initialized.
     */
    private Boolean startRepositoryImmediately = Boolean.TRUE;

    /**
     * Repository.
     */
    private transient Repository repository;

    /**
     * Log writer.
     */
    private transient PrintWriter logWriter;

    /**
     * Return the repository URI.
     */
    public String getRepositoryURI() {
        return parameters.get(JcrUtils.REPOSITORY_URI);
    }

    /**
     * Set the repository URI.
     */
    public void setRepositoryURI(String uri) {
        parameters.put(JcrUtils.REPOSITORY_URI, uri);
    }

    /**
     * Return the repository home directory.
     */
    public String getHomeDir() {
        return parameters.get(HOMEDIR_KEY);
    }

    /**
     * Set the repository home directory.
     */
    public void setHomeDir(String home) {
        parameters.put(HOMEDIR_KEY, home);
    }

    /**
     * Return the repository configuration file.
     */
    public String getConfigFile() {
        return parameters.get(CONFIGFILE_KEY);
    }

    /**
     * Set the repository configuration file.
     */
    public void setConfigFile(String conf) {
        parameters.put(CONFIGFILE_KEY, conf);
    }

    /**
     * Get the log writer.
     */
    public PrintWriter getLogWriter() {
        return logWriter;
    }

    /**
     * Set the log writer.
     */
    public void setLogWriter(PrintWriter logWriter)
            throws ResourceException {
        this.logWriter = logWriter;
    }

    /**
     * Creates a Connection Factory instance.
     */
    public Object createConnectionFactory()
            throws ResourceException {
        return createConnectionFactory(new JCAConnectionManager());
    }

    /**
     * Creates a Connection Factory instance.
     */
    public Object createConnectionFactory(ConnectionManager cm)
            throws ResourceException {
    	if (startRepositoryImmediately) {
    		createRepository();
    	}
        JCARepositoryHandle handle = new JCARepositoryHandle(this, cm);
        log("Created repository handle (" + handle + ")");
        return handle;
    }

    /**
     * {@inheritDoc}
     * <p/>
     * Creates a new physical connection to the underlying EIS resource manager.
     * <p/>
     * WebSphere 5.1.1 will try to recover an XA resource on startup, regardless
     * whether it was committed or rolled back. On this occasion, <code>cri</code>
     * will be <code>null</code>. In order to be interoperable, we return an
     * anonymous connection, whose XA resource is recoverable-only.
     */
    public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cri)
            throws ResourceException {

        if (cri == null) {
            return new AnonymousConnection();
        }
        return createManagedConnection((JCAConnectionRequestInfo) cri);
    }

    /**
     * Creates a new physical connection to the underlying EIS resource manager.
     */
    private ManagedConnection createManagedConnection(JCAConnectionRequestInfo cri)
            throws ResourceException {
        return new JCAManagedConnection(this, cri);
    }

    /**
     * Returns a matched connection from the candidate set of connections.
     */
    @SuppressWarnings("rawtypes")
	public ManagedConnection matchManagedConnections(
            Set set, Subject subject, ConnectionRequestInfo cri)
            throws ResourceException {
        for (Object connection : set) {
            if (connection instanceof JCAManagedConnection) {
                JCAManagedConnection mc = (JCAManagedConnection) connection;
                if (equals(mc.getManagedConnectionFactory())) {
                    JCAConnectionRequestInfo otherCri = mc.getConnectionRequestInfo();
                    if (cri == otherCri || (cri != null && cri.equals(otherCri))) {
                        return mc;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Create/startup the repository.
     */
    @SuppressWarnings("deprecation")
    private void createRepository() throws ResourceException {
        if (repository == null) {
            try {
                JCARepositoryManager mgr = JCARepositoryManager.getInstance();
                repository = mgr.createRepository(parameters);
                log("Created repository (" + repository + ")");
            } catch (RepositoryException e) {
                log("Failed to create repository", e);
                ResourceException exception = new ResourceException(
                        "Failed to create repository: " + e.getMessage());
                exception.setLinkedException(e);
                throw exception;
            }
        }
    }

    /**
     * Return the repository, automatically creating it if needed.
     */
    @SuppressWarnings("deprecation")
	public Repository getRepository() throws RepositoryException {
    	if (repository == null) {
    		synchronized (this) {
    			try {
    				createRepository();
    			} catch (ResourceException e) {
    				throw (RepositoryException) e.getLinkedException();
    			}
			}
    	}
        return repository;
    }
    
    /**
     * Log a message.
     */
    public void log(String message) {
        log(message, null);
    }

    /**
     * Log a message.
     */
    public void log(String message, Throwable exception) {
        if (logWriter != null) {
            logWriter.println(message);

            if (exception != null) {
                exception.printStackTrace(logWriter);
            }
        }
    }

    /**
     * Return the hash code.
     */
    public int hashCode() {
        return parameters.hashCode();
    }

    /**
     * Return true if equals.
     */
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof JCAManagedConnectionFactory) {
            return equals((JCAManagedConnectionFactory) o);
        } else {
            return false;
        }
    }

    /**
     * Return true if equals.
     */
    private boolean equals(JCAManagedConnectionFactory o) {
        return parameters.equals(o.parameters);
    }

    /**
     * Shutdown the repository.
     */
    protected void finalize() {
        JCARepositoryManager mgr = JCARepositoryManager.getInstance();
        mgr.autoShutdownRepository(parameters);
    }

    public Boolean getBindSessionToTransaction() {
        return bindSessionToTransaction;
    }

    public void setBindSessionToTransaction(Boolean bindSessionToTransaction) {
        this.bindSessionToTransaction = bindSessionToTransaction;
    }

    public Boolean getStartRepositoryImmediately() {
        return startRepositoryImmediately;
    }

    public void setStartRepositoryImmediately(Boolean startRepositoryImmediately) {
        this.startRepositoryImmediately = startRepositoryImmediately;
    }

}
