001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2018–2019 microBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.jpa.weld; 018 019import java.lang.annotation.Annotation; 020 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Objects; 028import java.util.Set; 029 030import java.util.concurrent.ConcurrentHashMap; 031 032import javax.enterprise.inject.literal.NamedLiteral; 033 034import javax.enterprise.inject.spi.Annotated; 035import javax.enterprise.inject.spi.AnnotatedField; 036import javax.enterprise.inject.spi.Bean; 037import javax.enterprise.inject.spi.BeanManager; 038import javax.enterprise.inject.spi.CDI; 039import javax.enterprise.inject.spi.InjectionPoint; 040 041import javax.persistence.EntityManager; 042import javax.persistence.EntityManagerFactory; 043import javax.persistence.Persistence; 044import javax.persistence.PersistenceContext; 045import javax.persistence.PersistenceException; 046import javax.persistence.PersistenceUnit; 047import javax.persistence.SynchronizationType; 048 049import javax.persistence.spi.PersistenceProvider; 050import javax.persistence.spi.PersistenceUnitInfo; 051import javax.persistence.spi.PersistenceUnitTransactionType; 052 053import org.jboss.weld.injection.spi.ResourceReference; 054import org.jboss.weld.injection.spi.ResourceReferenceFactory; 055 056/** 057 * A {@link org.jboss.weld.injection.spi.JpaInjectionServices} 058 * implementation that integrates JPA functionality into Weld-based 059 * CDI environments. 060 * 061 * @author <a href="https://about.me/lairdnelson" 062 * target="_parent">Laird Nelson</a> 063 * 064 * @see org.jboss.weld.injection.spi.JpaInjectionServices 065 */ 066public final class JpaInjectionServices implements org.jboss.weld.injection.spi.JpaInjectionServices { 067 068 069 /* 070 * Static fields. 071 */ 072 073 074 /* 075 * For unknown reasons, Weld instantiates this class three times 076 * during normal execution. Only one of those instances is actually 077 * used to produce EntityManagers and EntityManagerFactories. The 078 * INSTANCE and UNDERWAY fields ensure that truly only one instance 079 * processes all incoming calls. 080 * 081 * See the underway() method as well. 082 */ 083 084 /** 085 * The single officially sanctioned instance of this class. 086 * 087 * <p>This field may be {@code null}.</p> 088 */ 089 static volatile JpaInjectionServices INSTANCE; 090 091 private static volatile boolean UNDERWAY; 092 093 094 /* 095 * Instance fields. 096 */ 097 098 099 private final Set<EntityManager> ems; 100 101 private volatile Map<String, EntityManagerFactory> emfs; 102 103 104 /* 105 * Constructors. 106 */ 107 108 109 /** 110 * Creates a new {@link JpaInjectionServices}. 111 */ 112 public JpaInjectionServices() { 113 super(); 114 synchronized (JpaInjectionServices.class) { 115 if (INSTANCE != null && UNDERWAY) { 116 throw new IllegalStateException(); 117 } 118 INSTANCE = this; 119 } 120 this.ems = ConcurrentHashMap.newKeySet(); 121 } 122 123 private static synchronized final void underway() { 124 assert INSTANCE != null; 125 UNDERWAY = true; 126 } 127 128 /** 129 * Called by the ({@code private}) {@code 130 * JpaInjectionServicesExtension} class when a JTA transaction is 131 * begun. 132 * 133 * <p>The Narayana CDI integration this class is often deployed with 134 * will fire such events. These events serve as an indication that 135 * a call to {@link TransactionManager#begin()} has been made.</p> 136 * 137 * <p>{@link EntityManager}s created by this class will have their 138 * {@link EntityManager#joinTransaction()} methods called if the 139 * supplied object is non-{@code null}.</p> 140 * 141 * @param transaction an {@link Object} representing the 142 * transaction; may be {@code null} in which case no action will be 143 * taken 144 */ 145 final void jtaTransactionBegun(final Object transaction) { 146 if (this != INSTANCE) { 147 INSTANCE.jtaTransactionBegun(transaction); 148 } else if (transaction != null) { 149 ems.forEach(em -> em.joinTransaction()); 150 } 151 } 152 153 /** 154 * Returns a {@link ResourceReferenceFactory} whose {@link 155 * ResourceReferenceFactory#createResource()} method will be invoked 156 * appropriately by Weld later. 157 * 158 * <p>This method never returns {@code null}.</p> 159 * 160 * @param the {@link InjectionPoint} annotated with {@link 161 * PersistenceContext}; must not be {@code null} 162 * 163 * @return a non-{@code null} {@link ResourceReferenceFactory} whose 164 * {@link ResourceReferenceFactory#createResource()} method will 165 * create {@link EntityManager} instances 166 * 167 * @exception NullPointerException if {@code injectionPoint} is 168 * {@code null} 169 * 170 * @see ResourceReferenceFactory#createResource() 171 */ 172 @Override 173 public final ResourceReferenceFactory<EntityManager> registerPersistenceContextInjectionPoint(final InjectionPoint injectionPoint) { 174 underway(); 175 final ResourceReferenceFactory<EntityManager> returnValue; 176 if (this != INSTANCE) { 177 returnValue = INSTANCE.registerPersistenceContextInjectionPoint(injectionPoint); 178 } else { 179 Objects.requireNonNull(injectionPoint); 180 final Annotated annotatedMember = injectionPoint.getAnnotated(); 181 assert annotatedMember != null; 182 final PersistenceContext persistenceContextAnnotation = annotatedMember.getAnnotation(PersistenceContext.class); 183 if (persistenceContextAnnotation == null) { 184 throw new IllegalArgumentException("injectionPoint.getAnnotated().getAnnotation(PersistenceContext.class) == null"); 185 } 186 final String name; 187 final String n = persistenceContextAnnotation.unitName(); 188 if (n.isEmpty()) { 189 if (annotatedMember instanceof AnnotatedField) { 190 name = ((AnnotatedField<?>)annotatedMember).getJavaMember().getName(); 191 } else { 192 name = n; 193 } 194 } else { 195 name = n; 196 } 197 final SynchronizationType synchronizationType = persistenceContextAnnotation.synchronization(); 198 assert synchronizationType != null; 199 synchronized (this) { 200 if (this.emfs == null) { 201 this.emfs = new ConcurrentHashMap<>(); 202 } 203 } 204 returnValue = () -> new EntityManagerResourceReference(name, synchronizationType); 205 } 206 return returnValue; 207 } 208 209 /** 210 * Returns a {@link ResourceReferenceFactory} whose {@link 211 * ResourceReferenceFactory#createResource()} method will be invoked 212 * appropriately by Weld later. 213 * 214 * <p>This method never returns {@code null}.</p> 215 * 216 * @param the {@link InjectionPoint} annotated with {@link 217 * PersistenceUnit}; must not be {@code null} 218 * 219 * @return a non-{@code null} {@link ResourceReferenceFactory} whose 220 * {@link ResourceReferenceFactory#createResource()} method will 221 * create {@link EntityManagerFactory} instances 222 * 223 * @exception NullPointerException if {@code injectionPoint} is 224 * {@code null} 225 * 226 * @see ResourceReferenceFactory#createResource() 227 */ 228 @Override 229 public final ResourceReferenceFactory<EntityManagerFactory> registerPersistenceUnitInjectionPoint(final InjectionPoint injectionPoint) { 230 underway(); 231 final ResourceReferenceFactory<EntityManagerFactory> returnValue; 232 if (this != INSTANCE) { 233 returnValue = INSTANCE.registerPersistenceUnitInjectionPoint(injectionPoint); 234 } else { 235 Objects.requireNonNull(injectionPoint); 236 final Annotated annotatedMember = injectionPoint.getAnnotated(); 237 assert annotatedMember != null; 238 final PersistenceUnit persistenceUnitAnnotation = annotatedMember.getAnnotation(PersistenceUnit.class); 239 if (persistenceUnitAnnotation == null) { 240 throw new IllegalArgumentException("injectionPoint.getAnnotated().getAnnotation(PersistenceUnit.class) == null"); 241 } 242 final String name; 243 final String n = persistenceUnitAnnotation.unitName(); 244 if (n.isEmpty()) { 245 if (annotatedMember instanceof AnnotatedField) { 246 name = ((AnnotatedField<?>)annotatedMember).getJavaMember().getName(); 247 } else { 248 name = n; 249 } 250 } else { 251 name = n; 252 } 253 synchronized (this) { 254 if (this.emfs == null) { 255 this.emfs = new ConcurrentHashMap<>(); 256 } 257 } 258 returnValue = () -> new EntityManagerFactoryResourceReference(this.emfs, name); 259 } 260 return returnValue; 261 } 262 263 /** 264 * Invoked by Weld automatically to clean up any resources held by 265 * this class. 266 */ 267 @Override 268 public final void cleanup() { 269 underway(); 270 if (this != INSTANCE) { 271 INSTANCE.cleanup(); 272 } else { 273 ems.clear(); 274 final Map<? extends String, ? extends EntityManagerFactory> emfs = this.emfs; 275 if (emfs != null && !emfs.isEmpty()) { 276 final Collection<? extends Entry<? extends String, ? extends EntityManagerFactory>> entries = emfs.entrySet(); 277 assert entries != null; 278 assert !entries.isEmpty(); 279 final Iterator<? extends Entry<? extends String, ? extends EntityManagerFactory>> iterator = entries.iterator(); 280 assert iterator != null; 281 assert iterator.hasNext(); 282 while (iterator.hasNext()) { 283 final Entry<? extends String, ? extends EntityManagerFactory> entry = iterator.next(); 284 assert entry != null; 285 final EntityManagerFactory emf = entry.getValue(); 286 assert emf != null; 287 if (emf.isOpen()) { 288 emf.close(); 289 } 290 iterator.remove(); 291 } 292 } 293 } 294 } 295 296 /** 297 * Calls the {@link 298 * #registerPersistenceContextInjectionPoint(InjectionPoint)} method 299 * and invokes {@link ResourceReference#getInstance()} on its return 300 * value and returns the result. 301 * 302 * <p>This method never returns {@code null}.</p> 303 * 304 * @param injectionPoint an {@link InjectionPoint} annotated with 305 * {@link PersistenceContext}; must not be {@code null} 306 * 307 * @return a non-{@code null} {@link EntityManager} 308 * 309 * @see #registerPersistenceContextInjectionPoint(InjectionPoint) 310 */ 311 @Deprecated 312 @Override 313 public final EntityManager resolvePersistenceContext(final InjectionPoint injectionPoint) { 314 return this.registerPersistenceContextInjectionPoint(injectionPoint).createResource().getInstance(); 315 } 316 317 /** 318 * Calls the {@link 319 * #registerPersistenceUnitInjectionPoint(InjectionPoint)} method 320 * and invokes {@link ResourceReference#getInstance()} on its return 321 * value and returns the result. 322 * 323 * <p>This method never returns {@code null}.</p> 324 * 325 * @param injectionPoint an {@link InjectionPoint} annotated with 326 * {@link PersistenceUnit}; must not be {@code null} 327 * 328 * @return a non-{@code null} {@link EntityManagerFactory} 329 * 330 * @see #registerPersistenceUnitInjectionPoint(InjectionPoint) 331 */ 332 @Deprecated 333 @Override 334 public final EntityManagerFactory resolvePersistenceUnit(final InjectionPoint injectionPoint) { 335 return this.registerPersistenceUnitInjectionPoint(injectionPoint).createResource().getInstance(); 336 } 337 338 339 /* 340 * Static methods. 341 */ 342 343 344 private static final PersistenceProvider getPersistenceProvider(final PersistenceUnitInfo persistenceUnitInfo) { 345 final String providerClassName = Objects.requireNonNull(persistenceUnitInfo).getPersistenceProviderClassName(); 346 final PersistenceProvider persistenceProvider; 347 if (providerClassName == null) { 348 persistenceProvider = CDI.current().select(PersistenceProvider.class).get(); 349 } else { 350 try { 351 persistenceProvider = 352 (PersistenceProvider)CDI.current().select(Class.forName(providerClassName, 353 true, 354 Thread.currentThread().getContextClassLoader())).get(); 355 } catch (final ReflectiveOperationException exception) { 356 throw new PersistenceException(exception.getMessage(), exception); 357 } 358 } 359 return persistenceProvider; 360 } 361 362 private static final PersistenceUnitInfo getPersistenceUnitInfo(final String name) { 363 return CDI.current().select(PersistenceUnitInfo.class, 364 NamedLiteral.of(Objects.requireNonNull(name))).get(); 365 } 366 367 private static final EntityManagerFactory getOrCreateEntityManagerFactory(final Map<String, EntityManagerFactory> emfs, 368 final PersistenceUnitInfo persistenceUnitInfo, 369 final String name) { 370 Objects.requireNonNull(emfs); 371 Objects.requireNonNull(name); 372 final EntityManagerFactory returnValue; 373 if (persistenceUnitInfo == null) { 374 returnValue = 375 emfs.computeIfAbsent(name, 376 n -> Persistence.createEntityManagerFactory(n)); 377 378 } else { 379 final PersistenceProvider persistenceProvider = getPersistenceProvider(persistenceUnitInfo); 380 assert persistenceProvider != null; 381 returnValue = 382 emfs.computeIfAbsent(name, 383 n -> { 384 final CDI<Object> cdi = CDI.current(); 385 assert cdi != null; 386 final BeanManager beanManager = cdi.getBeanManager(); 387 assert beanManager != null; 388 final Map<String, Object> properties = new HashMap<>(); 389 properties.put("javax.persistence.bean.manager", 390 beanManager); 391 Class<?> validatorFactoryClass = null; 392 try { 393 validatorFactoryClass = Class.forName("javax.validation.ValidatorFactory"); 394 } catch (final ClassNotFoundException classNotFoundException) { 395 classNotFoundException.printStackTrace(); 396 } 397 if (validatorFactoryClass != null) { 398 final Bean<?> validatorFactoryBean = 399 getValidatorFactoryBean(beanManager, 400 validatorFactoryClass); 401 if (validatorFactoryBean != null) { 402 properties.put("javax.validation.ValidatorFactory", 403 beanManager.getReference(validatorFactoryBean, 404 validatorFactoryClass, 405 beanManager.createCreationalContext(validatorFactoryBean))); 406 } 407 } 408 return 409 persistenceProvider.createContainerEntityManagerFactory(persistenceUnitInfo, 410 properties); 411 }); 412 } 413 return returnValue; 414 } 415 416 417 /* 418 * Inner and nested classes. 419 */ 420 421 422 private static final class EntityManagerFactoryResourceReference implements ResourceReference<EntityManagerFactory> { 423 424 private final Map<String, EntityManagerFactory> emfs; 425 426 private final String name; 427 428 private EntityManagerFactoryResourceReference(final Map<String, EntityManagerFactory> emfs, 429 final String name) { 430 super(); 431 this.emfs = Objects.requireNonNull(emfs); 432 this.name = Objects.requireNonNull(name); 433 } 434 435 @Override 436 public final EntityManagerFactory getInstance() { 437 final PersistenceUnitInfo persistenceUnitInfo = getPersistenceUnitInfo(this.name); 438 assert persistenceUnitInfo != null; 439 final EntityManagerFactory returnValue; 440 if (PersistenceUnitTransactionType.RESOURCE_LOCAL.equals(persistenceUnitInfo.getTransactionType())) { 441 returnValue = getOrCreateEntityManagerFactory(emfs, null, name); 442 } else { 443 returnValue = getOrCreateEntityManagerFactory(emfs, persistenceUnitInfo, name); 444 } 445 return returnValue; 446 } 447 448 @Override 449 public final void release() { 450 final EntityManagerFactory emf = this.emfs.remove(this.name); 451 if (emf != null && emf.isOpen()) { 452 emf.close(); 453 } 454 } 455 } 456 457 private final class EntityManagerResourceReference implements ResourceReference<EntityManager> { 458 459 private final String name; 460 461 private final SynchronizationType synchronizationType; 462 463 private volatile EntityManager em; 464 465 private EntityManagerResourceReference(final String name, 466 final SynchronizationType synchronizationType) { 467 super(); 468 this.name = Objects.requireNonNull(name); 469 this.synchronizationType = Objects.requireNonNull(synchronizationType); 470 } 471 472 @Override 473 public final EntityManager getInstance() { 474 EntityManager returnValue = this.em; // atomic assignment 475 if (returnValue == null) { 476 final PersistenceUnitInfo persistenceUnitInfo = getPersistenceUnitInfo(this.name); 477 assert persistenceUnitInfo != null; 478 final EntityManagerFactory emf; 479 if (PersistenceUnitTransactionType.RESOURCE_LOCAL.equals(persistenceUnitInfo.getTransactionType())) { 480 emf = getOrCreateEntityManagerFactory(emfs, null, this.name); 481 assert emf != null; 482 returnValue = emf.createEntityManager(); 483 } else { 484 // JTA is in effect 485 emf = getOrCreateEntityManagerFactory(emfs, persistenceUnitInfo, this.name); 486 assert emf != null; 487 returnValue = emf.createEntityManager(this.synchronizationType); 488 } 489 assert returnValue != null; 490 this.em = returnValue; 491 ems.add(returnValue); 492 } 493 return returnValue; 494 } 495 496 @Override 497 public final void release() { 498 final EntityManager em = this.em; 499 if (em != null && em.isOpen()) { 500 em.close(); 501 } 502 } 503 504 } 505 506 private static final Bean<?> getValidatorFactoryBean(final BeanManager beanManager, 507 final Class<?> validatorFactoryClass) { 508 return getValidatorFactoryBean(beanManager, validatorFactoryClass, null); 509 } 510 511 private static final Bean<?> getValidatorFactoryBean(final BeanManager beanManager, 512 final Class<?> validatorFactoryClass, 513 final Set<Annotation> qualifiers) { 514 Bean<?> returnValue = null; 515 if (beanManager != null && validatorFactoryClass != null) { 516 final Set<Bean<?>> beans; 517 if (qualifiers == null) { 518 beans = beanManager.getBeans(validatorFactoryClass); 519 } else { 520 beans = beanManager.getBeans(validatorFactoryClass, qualifiers.toArray(new Annotation[qualifiers.size()])); 521 } 522 if (beans != null && !beans.isEmpty()) { 523 returnValue = beanManager.resolve(beans); 524 } 525 } 526 return returnValue; 527 } 528 529}