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}