/*
 * Copyright 2002-2008 the original author or authors.
 *
 * 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.springframework.samples.petclinic.config;

import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.ExternalBean;
import org.springframework.config.java.annotation.ExternalValue;
import org.springframework.config.java.annotation.Import;
import org.springframework.config.java.annotation.valuesource.PropertiesValueSource;
import org.springframework.config.java.support.ConfigurationSupport;
import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.samples.petclinic.Clinic;
import org.springframework.samples.petclinic.jpa.EntityManagerClinic;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.context.ContextLoaderListener;

/**
 * Application context definition for JPA-based Petclinic. This configuration is an
 * alternative to {@link JdbcPetclinicApplicationConfig}. See web.xml to see how
 * it is bootstrapped at webapp startup time.
 * <p/>
 * 
 * See {@link JdbcPetclinicApplicationConfig} for explanatory notes on the
 * {@code @Import}, {@code @PropertiesValueSource} and {@code @ExternalValue}
 * annotations.
 * <p/>
 * 
 * {@code @Import} behaves similarly to Spring XML's {@literal <import/>}
 * element.  In this case, in pulls in all {@code @Bean} definitions from
 * {@link PetclinicApplicationConfig} and {@link EmbeddedDataSourceConfig}.
 * These two configuration classes are broken up for modularity purposes:
 * {@code JpaPetclinicApplicationConfig} needs a {@code DataSource} object,
 * but it does not need to be concerned with how or where that object is configured.
 * See {@link EmbeddedDataSourceConfig} for further details.  Note that the
 * {@code @ExternalBean} {@link #dataSource()} will be overriden by the framework
 * at runtime to return the {@code DataSource} object provided by
 * {@link EmbeddedDataSourceConfig}.
 * <p/>
 * 
 * {@code @PropertiesValueSource} ensures that properties from db/jdbc.properties
 * will be read in and made available for resolution via {@code @ExternalValue} methods
 * and/or fields.
 *
 * @see PetclinicApplicationConfig
 * @see JdbcPetclinicApplicationConfig
 * @see EmbeddedDataSourceConfig
 * @see ExternalValue
 * @see PropertiesValueSource
 * @see src/main/resources/db/jdbc.properties
 * @see WEB-INF/web.xml for use in conjunction with {@link ContextLoaderListener}
 * 
 * @author Chris Beams
 * @author Arulazi Dhesiaseelan
 */
@Configuration
@Import({PetclinicApplicationConfig.class, EmbeddedDataSourceConfig.class})
@PropertiesValueSource(locations = "classpath:db/jdbc.properties")
public abstract class JpaPetclinicApplicationConfig extends ConfigurationSupport {
    
    // Each of the following @ExternalValues are provided by db/jdbc.properties
    abstract @ExternalValue("jpa.showSql") boolean showJpaSql();
    abstract @ExternalValue("jpa.database") String databaseType();
    abstract @ExternalValue("hibernate.show_sql") boolean showSql();
    abstract @ExternalValue("hibernate.generate_statistics") boolean generateStatistics();
    abstract @ExternalValue("hibernate.dialect") String databasePlatform();
    
    /** Provided by {@link EmbeddedDataSourceConfig#dataSource()} */
    abstract @ExternalBean DataSource dataSource();

    /**
     * PetClinic's central data access object using JPA EntityManager
     * <p/>
     * Note that {@link EntityManagerClinic} uses {@code @PersistenceContext} to
     * inject an {@link EntityManager} instance created by {@link #entityManagerFactory()}.
     */
    public @Bean Clinic clinic() {
        return new EntityManagerClinic();
    }

    /**
     * FactoryBean that creates a JPA EntityManagerFactory according to JPA's standard
     * container bootstrap contract.
     * 
     * @see ConfigurationSupport#getObject(FactoryBean)
     */
    public @Bean EntityManagerFactory entityManagerFactory() {
        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", databasePlatform());
        jpaProperties.put("hibernate.show_sql", showSql());
        jpaProperties.put("hibernate.generate_statistics", generateStatistics());
        
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setJpaProperties(jpaProperties);
        em.setDataSource(dataSource());
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setLoadTimeWeaver(loadTimeWeaver());
        return this.getObject(em, EntityManagerFactory.class);
    }

    /**
     * JpaVendorAdapter implementation for Hibernate EntityManager.
     */
    public @Bean JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(showJpaSql());
        adapter.setDatabasePlatform(databasePlatform());
        return adapter;
    }

    /**
     * LoadTimeWeaver relying on VM Instrumentation.
     */
    public @Bean LoadTimeWeaver loadTimeWeaver() {
        return new InstrumentationLoadTimeWeaver();
    }

    /**
     * PlatformTransactionManager implementation for a single JPA EntityManagerFactory.
     */
    public @Bean PlatformTransactionManager transactionManager() {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory());
        return txManager;
    }

}
