/*
 * 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 WARRANTIESOR 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.aries.jpa.container.impl;

import static org.osgi.service.jpa.EntityManagerFactoryBuilder.JPA_UNIT_NAME;
import static org.osgi.service.jpa.EntityManagerFactoryBuilder.JPA_UNIT_PROVIDER;
import static org.osgi.service.jpa.EntityManagerFactoryBuilder.JPA_UNIT_VERSION;

import java.io.Closeable;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Creates an EntityManagerFactory(EMF) for a persistence unit and publishes it as a service.
 * Custom properties can be configured by supplying a config admin configuriation named like
 * the JPA_CONFIGURATION_PREFIX.<persistence unit name>.
 */
public class ManagedEMF implements Closeable, ManagedService {
	private static final Logger LOGGER = LoggerFactory.getLogger(ManagedEMF.class);

    private static String JPA_CONFIGURATION_PREFIX = "org.apache.aries.jpa.";

    private EntityManagerFactory emf;
    private ServiceRegistration<EntityManagerFactory> reg;
    private PersistenceProvider provider;
    private PersistenceUnitInfo persistenceUnit;
    private Bundle bundle;
    private ServiceRegistration<?> configReg;

    private boolean closed;

    public ManagedEMF(BundleContext containerContext, Bundle bundle, PersistenceProvider provider, PersistenceUnitInfo persistenceUnit) {
        this.provider = provider;
        this.persistenceUnit = persistenceUnit;
        this.bundle = bundle;
        registerManagedService(containerContext, persistenceUnit);
        //createAndPublishEMF(null);
        closed = false;
    }

    private void registerManagedService(BundleContext containerContext, PersistenceUnitInfo persistenceUnit) {
        Dictionary<String, Object> configuration = new Hashtable<String, Object>();
        configuration.put(Constants.SERVICE_PID,
                          JPA_CONFIGURATION_PREFIX + persistenceUnit.getPersistenceUnitName());
        configReg = containerContext.registerService(ManagedService.class.getName(), this, configuration);
    }

    public void closeEMF() {
        if (reg != null) {
            try {

                reg.unregister();
            } catch (Exception e) {
                // Ignore. May happen if persistence unit bundle is unloaded/updated
            }
        }
        if (emf != null && emf.isOpen()) {
            try {
                emf.close();
            } catch (Exception e) {
                LOGGER.warn("Error closing EntityManagerFactory for " + persistenceUnit.getPersistenceUnitName(), e);
            }
        }
        reg = null;
        emf = null;
    }
    
    public void close() {
        closed = true;
        closeEMF();
        if (configReg != null) {
            configReg.unregister();
        }
    }

    @Override
    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
        if (closed) {
            return;
        }
        if (emf != null) {
            closeEMF();
        }
        if (bundle.getState() == Bundle.UNINSTALLED || bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.STOPPING) {
            // Not sure why but during the TCK tests updated sometimes was called
            // for uninstalled bundles
            return;
        }
        Map<String, Object> overrides = (properties != null) ? asMap(properties) : null;
        LOGGER.info("Registering EntityManagerFactory for persistence unit " + persistenceUnit.getPersistenceUnitName());
        if (LOGGER.isDebugEnabled()) {
           LOGGER.debug("Using properties override " + overrides); 
        }
        createAndPublishEMF(overrides);
    }

    private void createAndPublishEMF(Map<String, Object> overrides) {
        emf = provider.createContainerEntityManagerFactory(persistenceUnit, overrides);
        Dictionary<String, String> props = createProperties(persistenceUnit, bundle);
        BundleContext uctx = bundle.getBundleContext();
        reg = uctx.registerService(EntityManagerFactory.class, emf, props);
    }

    public static Dictionary<String, String> createProperties(PersistenceUnitInfo persistenceUnit, Bundle puBundle) {
        Dictionary<String, String> props = new Hashtable<String, String>();
        props.put(JPA_UNIT_NAME, persistenceUnit.getPersistenceUnitName());
        if (persistenceUnit.getPersistenceProviderClassName() != null) {
            props.put(JPA_UNIT_PROVIDER, persistenceUnit.getPersistenceProviderClassName());
        }
        props.put(JPA_UNIT_VERSION, puBundle.getVersion().toString());
        return props;
    }

    private Map<String, Object> asMap(Dictionary<String, ?> dict) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(PersistenceUnitTransactionType.class.getName(), persistenceUnit.getTransactionType());
        for (Enumeration<String> e = dict.keys(); e.hasMoreElements();) {
            String key = e.nextElement();
            map.put(key, dict.get(key));
        }
        return map;
    }

}
