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; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.ExecutionException; 033import java.util.concurrent.Future; 034 035import java.util.function.Supplier; 036 037import javax.enterprise.inject.literal.NamedLiteral; 038 039import javax.enterprise.inject.spi.Annotated; 040import javax.enterprise.inject.spi.AnnotatedField; 041import javax.enterprise.inject.spi.Bean; 042import javax.enterprise.inject.spi.BeanManager; 043import javax.enterprise.inject.spi.CDI; 044import javax.enterprise.inject.spi.InjectionPoint; 045 046import javax.persistence.EntityManager; 047import javax.persistence.EntityManagerFactory; 048import javax.persistence.Persistence; 049import javax.persistence.PersistenceContext; 050import javax.persistence.PersistenceException; 051import javax.persistence.PersistenceUnit; 052import javax.persistence.SynchronizationType; 053 054import javax.persistence.spi.PersistenceProvider; 055import javax.persistence.spi.PersistenceUnitInfo; 056import javax.persistence.spi.PersistenceUnitTransactionType; 057 058import org.jboss.weld.manager.api.ExecutorServices; 059import org.jboss.weld.manager.api.WeldManager; 060 061import org.jboss.weld.injection.spi.ResourceReference; 062import org.jboss.weld.injection.spi.ResourceReferenceFactory; 063 064import org.microbean.development.annotation.Issue; 065 066import static javax.persistence.spi.PersistenceUnitTransactionType.RESOURCE_LOCAL; 067 068/** 069 * A {@link org.jboss.weld.injection.spi.JpaInjectionServices} 070 * implementation that integrates JPA functionality into Weld-based 071 * CDI environments. 072 * 073 * @author <a href="https://about.me/lairdnelson" 074 * target="_parent">Laird Nelson</a> 075 * 076 * @see org.jboss.weld.injection.spi.JpaInjectionServices 077 */ 078@Issue(id = "WELD-2563", uri = "https://issues.jboss.org/browse/WELD-2563") 079public final class JpaInjectionServices implements org.jboss.weld.injection.spi.JpaInjectionServices { 080 081 082 /* 083 * Static fields. 084 */ 085 086 087 /* 088 * Weld instantiates this class three times during normal execution 089 * (see https://issues.jboss.org/browse/WELD-2563 for details). 090 * Only one of those instances (the first) is actually used to 091 * produce EntityManagers and EntityManagerFactories; the other two 092 * are discarded. The static INSTANCE and UNDERWAY fields ensure 093 * that truly only one instance processes all incoming calls, and 094 * that it is the one that is actually tracked and stored by Weld 095 * itself in the return value of the WeldManager#getServices() 096 * method. 097 * 098 * See the underway() method as well. 099 */ 100 101 /** 102 * The single officially sanctioned instance of this class. 103 * 104 * <p>This field may be {@code null}.</p> 105 */ 106 @Issue(id = "WELD_2563", uri = "https://issues.jboss.org/browse/WELD-2563") 107 static volatile JpaInjectionServices INSTANCE; 108 109 @Issue(id = "WELD_2563", uri = "https://issues.jboss.org/browse/WELD-2563") 110 private static volatile boolean UNDERWAY; 111 112 113 /* 114 * Instance fields. 115 */ 116 117 118 private final Set<EntityManager> ems; 119 120 // @GuardedBy("this") 121 private volatile Map<String, EntityManagerFactory> emfs; 122 123 124 /* 125 * Constructors. 126 */ 127 128 129 /** 130 * Creates a new {@link JpaInjectionServices}. 131 */ 132 public JpaInjectionServices() { 133 super(); 134 synchronized (JpaInjectionServices.class) { 135 // See https://issues.jboss.org/browse/WELD-2563. Make sure 136 // only the first instance is "kept" as it's the one tracked by 137 // WeldManager's ServiceRegistry. The others are discarded. 138 if (INSTANCE == null) { 139 assert !UNDERWAY; 140 INSTANCE = this; 141 } else if (UNDERWAY) { 142 throw new IllegalStateException(); 143 } 144 } 145 this.ems = ConcurrentHashMap.newKeySet(); 146 } 147 148 @Issue(id = "WELD_2563", uri = "https://issues.jboss.org/browse/WELD-2563") 149 private static synchronized final void underway() { 150 UNDERWAY = true; 151 } 152 153 /** 154 * Called by the ({@code private}) {@code 155 * JpaInjectionServicesExtension} class when a JTA transaction is 156 * begun. 157 * 158 * <p>The Narayana CDI integration this class is often deployed with 159 * will fire such events. These events serve as an indication that 160 * a call to {@link TransactionManager#begin()} has been made.</p> 161 * 162 * <p>{@link EntityManager}s created by this class will have their 163 * {@link EntityManager#joinTransaction()} methods called if the 164 * supplied object is non-{@code null}.</p> 165 */ 166 final void jtaTransactionBegun() { 167 assert this == INSTANCE; 168 this.ems.forEach(em -> em.joinTransaction()); 169 } 170 171 /** 172 * Returns a {@link ResourceReferenceFactory} whose {@link 173 * ResourceReferenceFactory#createResource()} method will be invoked 174 * appropriately by Weld later. 175 * 176 * <p>This method never returns {@code null}.</p> 177 * 178 * @param the {@link InjectionPoint} annotated with {@link 179 * PersistenceContext}; must not be {@code null} 180 * 181 * @return a non-{@code null} {@link ResourceReferenceFactory} whose 182 * {@link ResourceReferenceFactory#createResource()} method will 183 * create {@link EntityManager} instances 184 * 185 * @exception NullPointerException if {@code injectionPoint} is 186 * {@code null} 187 * 188 * @see ResourceReferenceFactory#createResource() 189 */ 190 @Override 191 public final ResourceReferenceFactory<EntityManager> registerPersistenceContextInjectionPoint(final InjectionPoint injectionPoint) { 192 underway(); 193 assert this == INSTANCE; 194 final ResourceReferenceFactory<EntityManager> returnValue; 195 Objects.requireNonNull(injectionPoint); 196 final Annotated annotatedMember = injectionPoint.getAnnotated(); 197 assert annotatedMember != null; 198 final PersistenceContext persistenceContextAnnotation = annotatedMember.getAnnotation(PersistenceContext.class); 199 if (persistenceContextAnnotation == null) { 200 throw new IllegalArgumentException("injectionPoint.getAnnotated().getAnnotation(PersistenceContext.class) == null"); 201 } 202 final String name; 203 final String n = persistenceContextAnnotation.unitName(); 204 if (n.isEmpty()) { 205 if (annotatedMember instanceof AnnotatedField) { 206 name = ((AnnotatedField<?>)annotatedMember).getJavaMember().getName(); 207 } else { 208 name = n; 209 } 210 } else { 211 name = n; 212 } 213 final SynchronizationType synchronizationType = persistenceContextAnnotation.synchronization(); 214 assert synchronizationType != null; 215 synchronized (this) { 216 if (this.emfs == null) { 217 this.emfs = new ConcurrentHashMap<>(); 218 } 219 } 220 returnValue = () -> new EntityManagerResourceReference(name, synchronizationType); 221 return returnValue; 222 } 223 224 /** 225 * Returns a {@link ResourceReferenceFactory} whose {@link 226 * ResourceReferenceFactory#createResource()} method will be invoked 227 * appropriately by Weld later. 228 * 229 * <p>This method never returns {@code null}.</p> 230 * 231 * @param the {@link InjectionPoint} annotated with {@link 232 * PersistenceUnit}; must not be {@code null} 233 * 234 * @return a non-{@code null} {@link ResourceReferenceFactory} whose 235 * {@link ResourceReferenceFactory#createResource()} method will 236 * create {@link EntityManagerFactory} instances 237 * 238 * @exception NullPointerException if {@code injectionPoint} is 239 * {@code null} 240 * 241 * @see ResourceReferenceFactory#createResource() 242 */ 243 @Override 244 public final ResourceReferenceFactory<EntityManagerFactory> registerPersistenceUnitInjectionPoint(final InjectionPoint injectionPoint) { 245 underway(); 246 assert this == INSTANCE; 247 final ResourceReferenceFactory<EntityManagerFactory> returnValue; 248 Objects.requireNonNull(injectionPoint); 249 final Annotated annotatedMember = injectionPoint.getAnnotated(); 250 assert annotatedMember != null; 251 final PersistenceUnit persistenceUnitAnnotation = annotatedMember.getAnnotation(PersistenceUnit.class); 252 if (persistenceUnitAnnotation == null) { 253 throw new IllegalArgumentException("injectionPoint.getAnnotated().getAnnotation(PersistenceUnit.class) == null"); 254 } 255 final String name; 256 final String n = persistenceUnitAnnotation.unitName(); 257 if (n.isEmpty()) { 258 if (annotatedMember instanceof AnnotatedField) { 259 name = ((AnnotatedField<?>)annotatedMember).getJavaMember().getName(); 260 } else { 261 name = n; 262 } 263 } else { 264 name = n; 265 } 266 synchronized (this) { 267 if (this.emfs == null) { 268 this.emfs = new ConcurrentHashMap<>(); 269 } 270 } 271 returnValue = () -> new EntityManagerFactoryResourceReference(this.emfs, name); 272 return returnValue; 273 } 274 275 /** 276 * Invoked by Weld automatically to clean up any resources held by 277 * this class. 278 */ 279 @Override 280 public final void cleanup() { 281 // cleanup() can get invoked multiple times at will by Weld. 282 // Specifically, the same Service instance can be stored in 283 // multiple BeanManagerImpls, and each one can call its cleanup() 284 // method, so it must be idempotent. 285 // 286 // See 287 // https://github.com/weld/core/blob/06fcaf4a6f625f101be5804208c1eb3a32884773/impl/src/main/java/org/jboss/weld/Container.java#L143-L145 288 // and 289 // https://github.com/weld/core/blob/06fcaf4a6f625f101be5804208c1eb3a32884773/impl/src/main/java/org/jboss/weld/manager/BeanManagerImpl.java#L1173. 290 if (UNDERWAY) { 291 assert this == INSTANCE; 292 293 // this.ems should be empty already. If for some reason it is 294 // not, we just clear() it (rather than, say, calling em.close() 295 // on each element). This is for two reasons: one, we're being 296 // cleaned up so the whole container is going down anyway. Two, 297 // it is forbidden by JPA's contract to call close() on a 298 // container-managed EntityManager...which is the only kind of 299 // EntityManager placed in this collection. 300 this.ems.clear(); 301 302 final Map<? extends String, ? extends EntityManagerFactory> emfs = this.emfs; 303 if (emfs != null && !emfs.isEmpty()) { 304 final Collection<? extends EntityManagerFactory> values = emfs.values(); 305 assert values != null; 306 assert !values.isEmpty(); 307 final Iterator<? extends EntityManagerFactory> iterator = values.iterator(); 308 assert iterator != null; 309 assert iterator.hasNext(); 310 while (iterator.hasNext()) { 311 final EntityManagerFactory emf = iterator.next(); 312 assert emf != null; 313 if (emf.isOpen()) { 314 emf.close(); 315 } 316 iterator.remove(); 317 } 318 } 319 } 320 assert this.ems.isEmpty(); 321 assert this.emfs == null || this.emfs.isEmpty(); 322 synchronized (JpaInjectionServices.class) { 323 UNDERWAY = false; 324 INSTANCE = null; 325 } 326 } 327 328 /** 329 * Calls the {@link 330 * #registerPersistenceContextInjectionPoint(InjectionPoint)} method 331 * and invokes {@link ResourceReference#getInstance()} on its return 332 * value and returns the result. 333 * 334 * <p>This method never returns {@code null}.</p> 335 * 336 * @param injectionPoint an {@link InjectionPoint} annotated with 337 * {@link PersistenceContext}; must not be {@code null} 338 * 339 * @return a non-{@code null} {@link EntityManager} 340 * 341 * @see #registerPersistenceContextInjectionPoint(InjectionPoint) 342 */ 343 @Deprecated 344 @Override 345 public final EntityManager resolvePersistenceContext(final InjectionPoint injectionPoint) { 346 return this.registerPersistenceContextInjectionPoint(injectionPoint).createResource().getInstance(); 347 } 348 349 /** 350 * Calls the {@link 351 * #registerPersistenceUnitInjectionPoint(InjectionPoint)} method 352 * and invokes {@link ResourceReference#getInstance()} on its return 353 * value and returns the result. 354 * 355 * <p>This method never returns {@code null}.</p> 356 * 357 * @param injectionPoint an {@link InjectionPoint} annotated with 358 * {@link PersistenceUnit}; must not be {@code null} 359 * 360 * @return a non-{@code null} {@link EntityManagerFactory} 361 * 362 * @see #registerPersistenceUnitInjectionPoint(InjectionPoint) 363 */ 364 @Deprecated 365 @Override 366 public final EntityManagerFactory resolvePersistenceUnit(final InjectionPoint injectionPoint) { 367 return this.registerPersistenceUnitInjectionPoint(injectionPoint).createResource().getInstance(); 368 } 369 370 371 /* 372 * Static methods. 373 */ 374 375 376 private static final PersistenceProvider getPersistenceProvider(final PersistenceUnitInfo persistenceUnitInfo) { 377 final String providerClassName = Objects.requireNonNull(persistenceUnitInfo).getPersistenceProviderClassName(); 378 final PersistenceProvider persistenceProvider; 379 if (providerClassName == null) { 380 persistenceProvider = CDI.current().select(PersistenceProvider.class).get(); 381 } else { 382 try { 383 persistenceProvider = 384 (PersistenceProvider)CDI.current().select(Class.forName(providerClassName, 385 true, 386 Thread.currentThread().getContextClassLoader())).get(); 387 } catch (final ReflectiveOperationException exception) { 388 throw new PersistenceException(exception.getMessage(), exception); 389 } 390 } 391 return persistenceProvider; 392 } 393 394 private static final PersistenceUnitInfo getPersistenceUnitInfo(final String name) { 395 return CDI.current().select(PersistenceUnitInfo.class, 396 NamedLiteral.of(Objects.requireNonNull(name))).get(); 397 } 398 399 private static final EntityManagerFactory getOrCreateEntityManagerFactory(final Map<String, EntityManagerFactory> emfs, 400 final PersistenceUnitInfo persistenceUnitInfo, 401 final String name) { 402 Objects.requireNonNull(emfs); 403 Objects.requireNonNull(name); 404 final EntityManagerFactory returnValue; 405 if (persistenceUnitInfo == null) { 406 returnValue = 407 emfs.computeIfAbsent(name, n -> Persistence.createEntityManagerFactory(n)); 408 } else { 409 final PersistenceProvider persistenceProvider = getPersistenceProvider(persistenceUnitInfo); 410 assert persistenceProvider != null; 411 returnValue = 412 emfs.computeIfAbsent(name, 413 n -> { 414 final CDI<Object> cdi = CDI.current(); 415 assert cdi != null; 416 final BeanManager beanManager = cdi.getBeanManager(); 417 assert beanManager != null; 418 final Map<String, Object> properties = new HashMap<>(); 419 properties.put("javax.persistence.bean.manager", 420 beanManager); 421 Class<?> validatorFactoryClass = null; 422 try { 423 validatorFactoryClass = Class.forName("javax.validation.ValidatorFactory"); 424 } catch (final ClassNotFoundException classNotFoundException) { 425 classNotFoundException.printStackTrace(); 426 } 427 if (validatorFactoryClass != null) { 428 final Bean<?> validatorFactoryBean = 429 getValidatorFactoryBean(beanManager, 430 validatorFactoryClass); 431 if (validatorFactoryBean != null) { 432 properties.put("javax.validation.ValidatorFactory", 433 beanManager.getReference(validatorFactoryBean, 434 validatorFactoryClass, 435 beanManager.createCreationalContext(validatorFactoryBean))); 436 } 437 } 438 return 439 persistenceProvider.createContainerEntityManagerFactory(persistenceUnitInfo, 440 properties); 441 }); 442 } 443 return returnValue; 444 } 445 446 private static final Bean<?> getValidatorFactoryBean(final BeanManager beanManager, 447 final Class<?> validatorFactoryClass) { 448 return getValidatorFactoryBean(beanManager, validatorFactoryClass, null); 449 } 450 451 private static final Bean<?> getValidatorFactoryBean(final BeanManager beanManager, 452 final Class<?> validatorFactoryClass, 453 final Set<Annotation> qualifiers) { 454 Bean<?> returnValue = null; 455 if (beanManager != null && validatorFactoryClass != null) { 456 final Set<Bean<?>> beans; 457 if (qualifiers == null) { 458 beans = beanManager.getBeans(validatorFactoryClass); 459 } else { 460 beans = beanManager.getBeans(validatorFactoryClass, qualifiers.toArray(new Annotation[qualifiers.size()])); 461 } 462 if (beans != null && !beans.isEmpty()) { 463 returnValue = beanManager.resolve(beans); 464 } 465 } 466 return returnValue; 467 } 468 469 470 /* 471 * Inner and nested classes. 472 */ 473 474 475 private static final class EntityManagerFactoryResourceReference implements ResourceReference<EntityManagerFactory> { 476 477 private final Map<String, EntityManagerFactory> emfs; 478 479 private final String name; 480 481 private final PersistenceUnitInfo persistenceUnitInfo; 482 483 private EntityManagerFactoryResourceReference(final Map<String, EntityManagerFactory> emfs, 484 final String name) { 485 super(); 486 this.emfs = Objects.requireNonNull(emfs); 487 this.name = Objects.requireNonNull(name); 488 this.persistenceUnitInfo = getPersistenceUnitInfo(name); 489 assert this.persistenceUnitInfo != null; 490 } 491 492 @Override 493 public final EntityManagerFactory getInstance() { 494 final EntityManagerFactory returnValue; 495 if (RESOURCE_LOCAL.equals(persistenceUnitInfo.getTransactionType())) { 496 returnValue = getOrCreateEntityManagerFactory(emfs, null, this.name); 497 } else { 498 returnValue = getOrCreateEntityManagerFactory(emfs, this.persistenceUnitInfo, this.name); 499 } 500 return returnValue; 501 } 502 503 @Override 504 public final void release() { 505 final EntityManagerFactory emf = this.emfs.remove(this.name); 506 if (emf != null && emf.isOpen()) { 507 emf.close(); 508 } 509 } 510 } 511 512 private final class EntityManagerResourceReference implements ResourceReference<EntityManager> { 513 514 private final String name; 515 516 private final SynchronizationType synchronizationType; 517 518 private final PersistenceUnitInfo persistenceUnitInfo; 519 520 // @GuardedBy("this") 521 private EntityManager em; 522 523 private final Future<EntityManagerFactory> emfFuture; 524 525 private final Supplier<EntityManager> emSupplier; 526 527 private EntityManagerResourceReference(final String name, 528 final SynchronizationType synchronizationType) { 529 super(); 530 this.name = Objects.requireNonNull(name); 531 this.synchronizationType = Objects.requireNonNull(synchronizationType); 532 this.persistenceUnitInfo = getPersistenceUnitInfo(name); 533 assert this.persistenceUnitInfo != null; 534 final ExecutorService taskExecutorService = ((WeldManager)CDI.current().getBeanManager()).getServices().get(ExecutorServices.class).getTaskExecutor(); 535 assert taskExecutorService != null; 536 if (this.isResourceLocal()) { 537 // Kick off the lengthy process of setting up an 538 // EntityManagerFactory in the background with the optimistic 539 // assumption, possibly incorrect, that someone will call 540 // getInstance() at some point. 541 this.emfFuture = taskExecutorService.submit(() -> { 542 return getOrCreateEntityManagerFactory(emfs, null, this.name); 543 }); 544 this.emSupplier = () -> { 545 try { 546 return emfFuture.get().createEntityManager(); 547 } catch (final ExecutionException executionException) { 548 throw new RuntimeException(executionException.getMessage(), executionException); 549 } catch (final InterruptedException interruptedException) { 550 Thread.currentThread().interrupt(); 551 throw new RuntimeException(interruptedException.getMessage(), interruptedException); 552 } 553 }; 554 } else { 555 // Kick off the lengthy process of setting up an 556 // EntityManagerFactory in the background with the optimistic 557 // assumption, possibly incorrect, that someone will call 558 // getInstance() at some point. 559 this.emfFuture = taskExecutorService.submit(() -> { 560 return getOrCreateEntityManagerFactory(emfs, this.persistenceUnitInfo, this.name); 561 }); 562 this.emSupplier = () -> { 563 try { 564 final EntityManager em = emfFuture.get().createEntityManager(this.synchronizationType); 565 JpaInjectionServices.this.ems.add(em); 566 return em; 567 } catch (final ExecutionException executionException) { 568 throw new RuntimeException(executionException.getMessage(), executionException); 569 } catch (final InterruptedException interruptedException) { 570 Thread.currentThread().interrupt(); 571 throw new RuntimeException(interruptedException.getMessage(), interruptedException); 572 } 573 }; 574 } 575 576 577 } 578 579 private final boolean isResourceLocal() { 580 return RESOURCE_LOCAL.equals(this.persistenceUnitInfo.getTransactionType()); 581 } 582 583 @Override 584 public synchronized final EntityManager getInstance() { 585 if (this.em == null) { 586 this.em = this.emSupplier.get(); 587 } 588 return this.em; 589 } 590 591 @Override 592 public final void release() { 593 final EntityManager em; 594 synchronized (this) { 595 em = this.em; 596 this.em = null; 597 } 598 if (em != null) { 599 if (em.isOpen() && this.isResourceLocal()) { 600 // Note that according to the javadocs on 601 // EntityManager#close(), you're never supposed to call 602 // EntityManager#close() on a container-managed 603 // EntityManager; hence the isResourceLocal() check here. 604 em.close(); 605 } 606 JpaInjectionServices.this.ems.remove(em); 607 } 608 if (!this.emfFuture.isDone()) { 609 this.emfFuture.cancel(true); 610 } 611 } 612 613 } 614 615}