    001// Copyright 2011 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.jpa;
    016
    017import java.io.InputStream;
    018import java.util.Collections;
    019import java.util.List;
    020import java.util.Map;
    021import java.util.Map.Entry;
    022import java.util.Set;
    023
    024import javax.persistence.EntityManager;
    025import javax.persistence.EntityManagerFactory;
    026import javax.persistence.spi.PersistenceProvider;
    027import javax.persistence.spi.PersistenceProviderResolver;
    028import javax.persistence.spi.PersistenceProviderResolverHolder;
    029import javax.persistence.spi.PersistenceUnitInfo;
    030
    031import org.apache.tapestry5.commons.Resource;
    032import org.apache.tapestry5.commons.util.CollectionFactory;
    033import org.apache.tapestry5.func.F;
    034import org.apache.tapestry5.func.Mapper;
    035import org.apache.tapestry5.func.Predicate;
    036import org.apache.tapestry5.ioc.annotations.Local;
    037import org.apache.tapestry5.ioc.annotations.PostInjection;
    038import org.apache.tapestry5.ioc.annotations.Symbol;
    039import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    040import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
    041import org.apache.tapestry5.jpa.EntityManagerSource;
    042import org.apache.tapestry5.jpa.JpaConstants;
    043import org.apache.tapestry5.jpa.JpaSymbols;
    044import org.apache.tapestry5.jpa.PersistenceUnitConfigurer;
    045import org.apache.tapestry5.jpa.TapestryPersistenceUnitInfo;
    046import org.slf4j.Logger;
    047
    048public class EntityManagerSourceImpl implements EntityManagerSource
    049{
    050    private final Map<String, EntityManagerFactory> entityManagerFactories = CollectionFactory
    051            .newMap();
    052
    053    private final Logger logger;
    054
    055    private final List<TapestryPersistenceUnitInfo> persistenceUnitInfos;
    056
    057    public EntityManagerSourceImpl(Logger logger, @Symbol(JpaSymbols.PERSISTENCE_DESCRIPTOR)
    058    final Resource persistenceDescriptor, @Local
    059    PersistenceUnitConfigurer packageNamePersistenceUnitConfigurer,
    060                                   Map<String, PersistenceUnitConfigurer> configuration)
    061    {
    062        this.logger = logger;
    063
    064        List<TapestryPersistenceUnitInfo> persistenceUnitInfos = parsePersistenceUnitInfos(persistenceDescriptor);
    065
    066        final Map<String, PersistenceUnitConfigurer> remainingConfigurations = configure(configuration, persistenceUnitInfos);
    067
    068        configureRemaining(persistenceUnitInfos, remainingConfigurations);
    069
    070        if (persistenceUnitInfos.size() == 1)
    071        {
    072            packageNamePersistenceUnitConfigurer.configure(persistenceUnitInfos.get(0));
    073        } else
    074        {
    075            validateUnitInfos(persistenceUnitInfos);
    076        }
    077
    078        this.persistenceUnitInfos = persistenceUnitInfos;
    079    }
    080
    081    @PostInjection
    082    public void listenForShutdown(RegistryShutdownHub hub)
    083    {
    084        hub.addRegistryShutdownListener(new Runnable()
    085        {
    086            @Override
    087            public void run()
    088            {
    089                registryDidShutdown();
    090            }
    091        });
    092    }
    093
    094    private void validateUnitInfos(List<TapestryPersistenceUnitInfo> persistenceUnitInfos)
    095    {
    096        final List<String> affectedUnits = F.flow(persistenceUnitInfos).filter(new Predicate<TapestryPersistenceUnitInfo>()
    097        {
    098            @Override
    099            public boolean accept(TapestryPersistenceUnitInfo info)
    100            {
    101                return !info.excludeUnlistedClasses();
    102            }
    103        }).map(new Mapper<TapestryPersistenceUnitInfo, String>()
    104        {
    105            @Override
    106            public String map(TapestryPersistenceUnitInfo info)
    107            {
    108                return info.getPersistenceUnitName();
    109            }
    110        }).toList();
    111
    112        if (0 < affectedUnits.size())
    113        {
    114            throw new RuntimeException(
    115                    String.format(
    116                            "Persistence units '%s' are configured to include managed classes that have not been explicitly listed. " +
    117                                    "This is forbidden when multiple persistence units are used in the same application. " +
    118                                    "Please configure persistence units to exclude unlisted managed classes (e.g. by removing <exclude-unlisted-classes> element) " +
    119                                    "and include them explicitly.",
    120                            InternalUtils.join(affectedUnits)));
    121        }
    122    }
    123
    124    private List<TapestryPersistenceUnitInfo> parsePersistenceUnitInfos(Resource persistenceDescriptor)
    125    {
    126        List<TapestryPersistenceUnitInfo> persistenceUnitInfos = CollectionFactory.newList();
    127
    128        if (persistenceDescriptor.exists())
    129        {
    130            final PersistenceParser parser = new PersistenceParser();
    131
    132            InputStream inputStream = null;
    133            try
    134            {
    135                inputStream = persistenceDescriptor.openStream();
    136                persistenceUnitInfos = parser.parse(inputStream);
    137            } catch (Exception e)
    138            {
    139                throw new RuntimeException(e);
    140            } finally
    141            {
    142                InternalUtils.close(inputStream);
    143            }
    144
    145        }
    146        return persistenceUnitInfos;
    147    }
    148
    149    private Map<String, PersistenceUnitConfigurer> configure(Map<String, PersistenceUnitConfigurer> configuration, List<TapestryPersistenceUnitInfo> persistenceUnitInfos)
    150    {
    151        final Map<String, PersistenceUnitConfigurer> remainingConfigurations = CollectionFactory.newMap(configuration);
    152
    153        for (final TapestryPersistenceUnitInfo info : persistenceUnitInfos)
    154        {
    155            final String unitName = info.getPersistenceUnitName();
    156
    157            final PersistenceUnitConfigurer configurer = configuration.get(unitName);
    158
    159            if (configurer != null)
    160            {
    161                configurer.configure(info);
    162
    163                remainingConfigurations.remove(unitName);
    164            }
    165        }
    166
    167        return remainingConfigurations;
    168    }
    169
    170
    171    private void configureRemaining(List<TapestryPersistenceUnitInfo> persistenceUnitInfos, Map<String, PersistenceUnitConfigurer> remainingConfigurations)
    172    {
    173        for (Entry<String, PersistenceUnitConfigurer> entry : remainingConfigurations.entrySet())
    174        {
    175            final PersistenceUnitInfoImpl info = new PersistenceUnitInfoImpl(entry.getKey());
    176
    177            final PersistenceUnitConfigurer configurer = entry.getValue();
    178            configurer.configure(info);
    179
    180            persistenceUnitInfos.add(info);
    181        }
    182    }
    183
    184    /**
    185     * {@inheritDoc}
    186     */
    187    @Override
    188    public EntityManagerFactory getEntityManagerFactory(final String persistenceUnitName)
    189    {
    190        EntityManagerFactory emf = entityManagerFactories.get(persistenceUnitName);
    191
    192        if (emf == null)
    193            synchronized (this)
    194            {
    195                emf = entityManagerFactories.get(persistenceUnitName);
    196
    197                if (emf == null)
    198                {
    199
    200                    emf = createEntityManagerFactory(persistenceUnitName);
    201
    202                    entityManagerFactories.put(persistenceUnitName, emf);
    203                }
    204            }
    205
    206        return emf;
    207    }
    208
    209    @SuppressWarnings("unchecked")
    210    EntityManagerFactory createEntityManagerFactory(final String persistenceUnitName)
    211    {
    212
    213        for (final TapestryPersistenceUnitInfo info : persistenceUnitInfos)
    214        {
    215            if (info.getPersistenceUnitName().equals(persistenceUnitName))
    216            {
    217                final Map properties = info.getEntityManagerProperties() == null ? CollectionFactory.newCaseInsensitiveMap() : info.getEntityManagerProperties();
    218                properties.put(JpaConstants.PERSISTENCE_UNIT_NAME, persistenceUnitName);
    219
    220                String providerClassName = info.getPersistenceProviderClassName();
    221
    222                final PersistenceProvider persistenceProvider = getPersistenceProvider(persistenceUnitName, providerClassName);
    223
    224                return persistenceProvider.createContainerEntityManagerFactory(info, properties);
    225            }
    226        }
    227
    228        throw new IllegalStateException(String.format(
    229                "Failed to create EntityManagerFactory for persistence unit '%s'",
    230                persistenceUnitName));
    231    }
    232
    233    private PersistenceProvider getPersistenceProvider(final String persistenceUnitName, final String providerClassName)
    234    {
    235        final PersistenceProviderResolver resolver = PersistenceProviderResolverHolder
    236                .getPersistenceProviderResolver();
    237
    238        final List<PersistenceProvider> providers = resolver.getPersistenceProviders();
    239
    240        if (providers.isEmpty())
    241        {
    242            throw new IllegalStateException(
    243                    "No PersistenceProvider implementation available in the runtime environment.");
    244        }
    245
    246        if(1 < providers.size() && providerClassName == null)
    247        {
    248            throw new IllegalStateException(
    249                    String.format("Persistence providers [%s] are available in the runtime environment " +
    250                            "but no provider class is defined for the persistence unit %s.", InternalUtils.join(toProviderClasses(providers)), persistenceUnitName));
    251        }
    252
    253        if(providerClassName != null)
    254        {
    255            return findPersistenceProviderByName(providers, providerClassName);
    256        }
    257
    258        return providers.get(0);
    259    }
    260
    261    private PersistenceProvider findPersistenceProviderByName(final List<PersistenceProvider> providers, final String providerClassName)
    262    {
    263        PersistenceProvider provider = F.flow(providers).filter(new Predicate<PersistenceProvider>() {
    264            @Override
    265            public boolean accept(PersistenceProvider next) {
    266                return next.getClass().getName().equals(providerClassName);
    267            }
    268        }).first();
    269
    270        if(provider == null)
    271        {
    272            throw new IllegalStateException(
    273                    String.format("No persistence provider of type %s found in the runtime environment. " +
    274                            "Following providers are available: [%s]", providerClassName, InternalUtils.join(toProviderClasses(providers))));
    275        }
    276
    277        return provider;
    278    }
    279
    280    private List<Class> toProviderClasses(final List<PersistenceProvider> providers)
    281    {
    282       return F.flow(providers).map(new Mapper<PersistenceProvider, Class>() {
    283           @Override
    284           public Class map(PersistenceProvider element) {
    285               return element.getClass();
    286           }
    287       }).toList();
    288    }
    289
    290    @Override
    291    public EntityManager create(final String persistenceUnitName)
    292    {
    293        return getEntityManagerFactory(persistenceUnitName).createEntityManager();
    294    }
    295
    296    private void registryDidShutdown()
    297    {
    298        final Set<Entry<String, EntityManagerFactory>> entrySet = entityManagerFactories.entrySet();
    299
    300        for (final Entry<String, EntityManagerFactory> entry : entrySet)
    301        {
    302            final EntityManagerFactory emf = entry.getValue();
    303            try
    304            {
    305                emf.close();
    306            } catch (final Exception e)
    307            {
    308                logger.error(String.format(
    309                        "Failed to close EntityManagerFactory for persistence unit '%s'",
    310                        entry.getKey()), e);
    311            }
    312        }
    313
    314        entityManagerFactories.clear();
    315
    316    }
    317
    318    @Override
    319    public List<PersistenceUnitInfo> getPersistenceUnitInfos()
    320    {
    321        return Collections.<PersistenceUnitInfo>unmodifiableList(persistenceUnitInfos);
    322    }
    323
    324}