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