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