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}