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    
015    package org.apache.tapestry5.internal.jpa;
016    
017    import org.apache.tapestry5.func.F;
018    import org.apache.tapestry5.func.Mapper;
019    import org.apache.tapestry5.func.Predicate;
020    import org.apache.tapestry5.ioc.Resource;
021    import org.apache.tapestry5.ioc.annotations.Local;
022    import org.apache.tapestry5.ioc.annotations.PostInjection;
023    import org.apache.tapestry5.ioc.annotations.Symbol;
024    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
025    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
026    import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
027    import org.apache.tapestry5.jpa.*;
028    import org.slf4j.Logger;
029    
030    import javax.persistence.EntityManager;
031    import javax.persistence.EntityManagerFactory;
032    import javax.persistence.spi.PersistenceProvider;
033    import javax.persistence.spi.PersistenceProviderResolver;
034    import javax.persistence.spi.PersistenceProviderResolverHolder;
035    import javax.persistence.spi.PersistenceUnitInfo;
036    import java.io.InputStream;
037    import java.util.Collections;
038    import java.util.List;
039    import java.util.Map;
040    import java.util.Map.Entry;
041    import java.util.Set;
042    
043    public 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                @Override
249                public boolean accept(PersistenceProvider next) {
250                    return next.getClass().getName().equals(providerClassName);
251                }
252            }).first();
253    
254            if(provider == null)
255            {
256                throw new IllegalStateException(
257                        String.format("No persistence provider of type %s found in the runtime environment. " +
258                                "Following providers are available: [%s]", providerClassName, InternalUtils.join(toProviderClasses(providers))));
259            }
260    
261            return provider;
262        }
263    
264        private List<Class> toProviderClasses(final List<PersistenceProvider> providers)
265        {
266           return F.flow(providers).map(new Mapper<PersistenceProvider, Class>() {
267               @Override
268               public Class map(PersistenceProvider element) {
269                   return element.getClass();
270               }
271           }).toList();
272        }
273    
274        public EntityManager create(final String persistenceUnitName)
275        {
276            return getEntityManagerFactory(persistenceUnitName).createEntityManager();
277        }
278    
279        private void registryDidShutdown()
280        {
281            final Set<Entry<String, EntityManagerFactory>> entrySet = entityManagerFactories.entrySet();
282    
283            for (final Entry<String, EntityManagerFactory> entry : entrySet)
284            {
285                final EntityManagerFactory emf = entry.getValue();
286                try
287                {
288                    emf.close();
289                } catch (final Exception e)
290                {
291                    logger.error(String.format(
292                            "Failed to close EntityManagerFactory for persistence unit '%s'",
293                            entry.getKey()), e);
294                }
295            }
296    
297            entityManagerFactories.clear();
298    
299        }
300    
301        public List<PersistenceUnitInfo> getPersistenceUnitInfos()
302        {
303            return Collections.<PersistenceUnitInfo>unmodifiableList(persistenceUnitInfos);
304        }
305    
306    }