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 * @param transaction an {@link Object} representing the 167 * transaction; may be {@code null} in which case no action will be 168 * taken 169 */ 170 final void jtaTransactionBegun(final Object transaction) { 171 assert this == INSTANCE; 172 this.ems.forEach(em -> em.joinTransaction()); 173 } 174 175 /** 176 * Returns a {@link ResourceReferenceFactory} whose {@link 177 * ResourceReferenceFactory#createResource()} method will be invoked 178 * appropriately by Weld later. 179 * 180 * <p>This method never returns {@code null}.</p> 181 * 182 * @param the {@link InjectionPoint} annotated with {@link 183 * PersistenceContext}; must not be {@code null} 184 * 185 * @return a non-{@code null} {@link ResourceReferenceFactory} whose 186 * {@link ResourceReferenceFactory#createResource()} method will 187 * create {@link EntityManager} instances 188 * 189 * @exception NullPointerException if {@code injectionPoint} is 190 * {@code null} 191 * 192 * @see ResourceReferenceFactory#createResource() 193 */ 194 @Override 195 public final ResourceReferenceFactory<EntityManager> registerPersistenceContextInjectionPoint(final InjectionPoint injectionPoint) { 196 underway(); 197 assert this == INSTANCE; 198 final ResourceReferenceFactory<EntityManager> returnValue; 199 Objects.requireNonNull(injectionPoint); 200 final Annotated annotatedMember = injectionPoint.getAnnotated(); 201 assert annotatedMember != null; 202 final PersistenceContext persistenceContextAnnotation = annotatedMember.getAnnotation(PersistenceContext.class); 203 if (persistenceContextAnnotation == null) { 204 throw new IllegalArgumentException("injectionPoint.getAnnotated().getAnnotation(PersistenceContext.class) == null"); 205 } 206 final String name; 207 final String n = persistenceContextAnnotation.unitName(); 208 if (n.isEmpty()) { 209 if (annotatedMember instanceof AnnotatedField) { 210 name = ((AnnotatedField<?>)annotatedMember).getJavaMember().getName(); 211 } else { 212 name = n; 213 } 214 } else { 215 name = n; 216 } 217 final SynchronizationType synchronizationType = persistenceContextAnnotation.synchronization(); 218 assert synchronizationType != null; 219 synchronized (this) { 220 if (this.emfs == null) { 221 this.emfs = new ConcurrentHashMap<>(); 222 } 223 } 224 returnValue = () -> new EntityManagerResourceReference(name, synchronizationType); 225 return returnValue; 226 } 227 228 /** 229 * Returns a {@link ResourceReferenceFactory} whose {@link 230 * ResourceReferenceFactory#createResource()} method will be invoked 231 * appropriately by Weld later. 232 * 233 * <p>This method never returns {@code null}.</p> 234 * 235 * @param the {@link InjectionPoint} annotated with {@link 236 * PersistenceUnit}; must not be {@code null} 237 * 238 * @return a non-{@code null} {@link ResourceReferenceFactory} whose 239 * {@link ResourceReferenceFactory#createResource()} method will 240 * create {@link EntityManagerFactory} instances 241 * 242 * @exception NullPointerException if {@code injectionPoint} is 243 * {@code null} 244 * 245 * @see ResourceReferenceFactory#createResource() 246 */ 247 @Override 248 public final ResourceReferenceFactory<EntityManagerFactory> registerPersistenceUnitInjectionPoint(final InjectionPoint injectionPoint) { 249 underway(); 250 assert this == INSTANCE; 251 final ResourceReferenceFactory<EntityManagerFactory> returnValue; 252 Objects.requireNonNull(injectionPoint); 253 final Annotated annotatedMember = injectionPoint.getAnnotated(); 254 assert annotatedMember != null; 255 final PersistenceUnit persistenceUnitAnnotation = annotatedMember.getAnnotation(PersistenceUnit.class); 256 if (persistenceUnitAnnotation == null) { 257 throw new IllegalArgumentException("injectionPoint.getAnnotated().getAnnotation(PersistenceUnit.class) == null"); 258 } 259 final String name; 260 final String n = persistenceUnitAnnotation.unitName(); 261 if (n.isEmpty()) { 262 if (annotatedMember instanceof AnnotatedField) { 263 name = ((AnnotatedField<?>)annotatedMember).getJavaMember().getName(); 264 } else { 265 name = n; 266 } 267 } else { 268 name = n; 269 } 270 synchronized (this) { 271 if (this.emfs == null) { 272 this.emfs = new ConcurrentHashMap<>(); 273 } 274 } 275 returnValue = () -> new EntityManagerFactoryResourceReference(this.emfs, name); 276 return returnValue; 277 } 278 279 /** 280 * Invoked by Weld automatically to clean up any resources held by 281 * this class. 282 */ 283 @Override 284 public final void cleanup() { 285 // cleanup() can get invoked multiple times at will by Weld. 286 // Specifically, the same Service instance can be stored in 287 // multiple BeanManagerImpls, and each one can call its cleanup() 288 // method, so it must be idempotent. 289 // 290 // See 291 // https://github.com/weld/core/blob/06fcaf4a6f625f101be5804208c1eb3a32884773/impl/src/main/java/org/jboss/weld/Container.java#L143-L145 292 // and 293 // https://github.com/weld/core/blob/06fcaf4a6f625f101be5804208c1eb3a32884773/impl/src/main/java/org/jboss/weld/manager/BeanManagerImpl.java#L1173. 294 if (UNDERWAY) { 295 assert this == INSTANCE; 296 297 // this.ems should be empty already. If for some reason it is 298 // not, we just clear() it (rather than, say, calling em.close() 299 // on each element). This is for two reasons: one, we're being 300 // cleaned up so the whole container is going down anyway. Two, 301 // it is forbidden by JPA's contract to call close() on a 302 // container-managed EntityManager...which is the only kind of 303 // EntityManager placed in this collection. 304 this.ems.clear(); 305 306 final Map<? extends String, ? extends EntityManagerFactory> emfs = this.emfs; 307 if (emfs != null && !emfs.isEmpty()) { 308 final Collection<? extends EntityManagerFactory> values = emfs.values(); 309 assert values != null; 310 assert !values.isEmpty(); 311 final Iterator<? extends EntityManagerFactory> iterator = values.iterator(); 312 assert iterator != null; 313 assert iterator.hasNext(); 314 while (iterator.hasNext()) { 315 final EntityManagerFactory emf = iterator.next(); 316 assert emf != null; 317 if (emf.isOpen()) { 318 emf.close(); 319 } 320 iterator.remove(); 321 } 322 } 323 } 324 assert this.ems.isEmpty(); 325 assert this.emfs == null || this.emfs.isEmpty(); 326 synchronized (JpaInjectionServices.class) { 327 UNDERWAY = false; 328 INSTANCE = null; 329 } 330 } 331 332 /** 333 * Calls the {@link 334 * #registerPersistenceContextInjectionPoint(InjectionPoint)} method 335 * and invokes {@link ResourceReference#getInstance()} on its return 336 * value and returns the result. 337 * 338 * <p>This method never returns {@code null}.</p> 339 * 340 * @param injectionPoint an {@link InjectionPoint} annotated with 341 * {@link PersistenceContext}; must not be {@code null} 342 * 343 * @return a non-{@code null} {@link EntityManager} 344 * 345 * @see #registerPersistenceContextInjectionPoint(InjectionPoint) 346 */ 347 @Deprecated 348 @Override 349 public final EntityManager resolvePersistenceContext(final InjectionPoint injectionPoint) { 350 return this.registerPersistenceContextInjectionPoint(injectionPoint).createResource().getInstance(); 351 } 352 353 /** 354 * Calls the {@link 355 * #registerPersistenceUnitInjectionPoint(InjectionPoint)} method 356 * and invokes {@link ResourceReference#getInstance()} on its return 357 * value and returns the result. 358 * 359 * <p>This method never returns {@code null}.</p> 360 * 361 * @param injectionPoint an {@link InjectionPoint} annotated with 362 * {@link PersistenceUnit}; must not be {@code null} 363 * 364 * @return a non-{@code null} {@link EntityManagerFactory} 365 * 366 * @see #registerPersistenceUnitInjectionPoint(InjectionPoint) 367 */ 368 @Deprecated 369 @Override 370 public final EntityManagerFactory resolvePersistenceUnit(final InjectionPoint injectionPoint) { 371 return this.registerPersistenceUnitInjectionPoint(injectionPoint).createResource().getInstance(); 372 } 373 374 375 /* 376 * Static methods. 377 */ 378 379 380 private static final PersistenceProvider getPersistenceProvider(final PersistenceUnitInfo persistenceUnitInfo) { 381 final String providerClassName = Objects.requireNonNull(persistenceUnitInfo).getPersistenceProviderClassName(); 382 final PersistenceProvider persistenceProvider; 383 if (providerClassName == null) { 384 persistenceProvider = CDI.current().select(PersistenceProvider.class).get(); 385 } else { 386 try { 387 persistenceProvider = 388 (PersistenceProvider)CDI.current().select(Class.forName(providerClassName, 389 true, 390 Thread.currentThread().getContextClassLoader())).get(); 391 } catch (final ReflectiveOperationException exception) { 392 throw new PersistenceException(exception.getMessage(), exception); 393 } 394 } 395 return persistenceProvider; 396 } 397 398 private static final PersistenceUnitInfo getPersistenceUnitInfo(final String name) { 399 return CDI.current().select(PersistenceUnitInfo.class, 400 NamedLiteral.of(Objects.requireNonNull(name))).get(); 401 } 402 403 private static final EntityManagerFactory getOrCreateEntityManagerFactory(final Map<String, EntityManagerFactory> emfs, 404 final PersistenceUnitInfo persistenceUnitInfo, 405 final String name) { 406 Objects.requireNonNull(emfs); 407 Objects.requireNonNull(name); 408 final EntityManagerFactory returnValue; 409 if (persistenceUnitInfo == null) { 410 returnValue = 411 emfs.computeIfAbsent(name, n -> Persistence.createEntityManagerFactory(n)); 412 } else { 413 final PersistenceProvider persistenceProvider = getPersistenceProvider(persistenceUnitInfo); 414 assert persistenceProvider != null; 415 returnValue = 416 emfs.computeIfAbsent(name, 417 n -> { 418 final CDI<Object> cdi = CDI.current(); 419 assert cdi != null; 420 final BeanManager beanManager = cdi.getBeanManager(); 421 assert beanManager != null; 422 final Map<String, Object> properties = new HashMap<>(); 423 properties.put("javax.persistence.bean.manager", 424 beanManager); 425 Class<?> validatorFactoryClass = null; 426 try { 427 validatorFactoryClass = Class.forName("javax.validation.ValidatorFactory"); 428 } catch (final ClassNotFoundException classNotFoundException) { 429 classNotFoundException.printStackTrace(); 430 } 431 if (validatorFactoryClass != null) { 432 final Bean<?> validatorFactoryBean = 433 getValidatorFactoryBean(beanManager, 434 validatorFactoryClass); 435 if (validatorFactoryBean != null) { 436 properties.put("javax.validation.ValidatorFactory", 437 beanManager.getReference(validatorFactoryBean, 438 validatorFactoryClass, 439 beanManager.createCreationalContext(validatorFactoryBean))); 440 } 441 } 442 return 443 persistenceProvider.createContainerEntityManagerFactory(persistenceUnitInfo, 444 properties); 445 }); 446 } 447 return returnValue; 448 } 449 450 private static final Bean<?> getValidatorFactoryBean(final BeanManager beanManager, 451 final Class<?> validatorFactoryClass) { 452 return getValidatorFactoryBean(beanManager, validatorFactoryClass, null); 453 } 454 455 private static final Bean<?> getValidatorFactoryBean(final BeanManager beanManager, 456 final Class<?> validatorFactoryClass, 457 final Set<Annotation> qualifiers) { 458 Bean<?> returnValue = null; 459 if (beanManager != null && validatorFactoryClass != null) { 460 final Set<Bean<?>> beans; 461 if (qualifiers == null) { 462 beans = beanManager.getBeans(validatorFactoryClass); 463 } else { 464 beans = beanManager.getBeans(validatorFactoryClass, qualifiers.toArray(new Annotation[qualifiers.size()])); 465 } 466 if (beans != null && !beans.isEmpty()) { 467 returnValue = beanManager.resolve(beans); 468 } 469 } 470 return returnValue; 471 } 472 473 474 /* 475 * Inner and nested classes. 476 */ 477 478 479 private static final class EntityManagerFactoryResourceReference implements ResourceReference<EntityManagerFactory> { 480 481 private final Map<String, EntityManagerFactory> emfs; 482 483 private final String name; 484 485 private final PersistenceUnitInfo persistenceUnitInfo; 486 487 private EntityManagerFactoryResourceReference(final Map<String, EntityManagerFactory> emfs, 488 final String name) { 489 super(); 490 this.emfs = Objects.requireNonNull(emfs); 491 this.name = Objects.requireNonNull(name); 492 this.persistenceUnitInfo = getPersistenceUnitInfo(name); 493 assert this.persistenceUnitInfo != null; 494 } 495 496 @Override 497 public final EntityManagerFactory getInstance() { 498 final EntityManagerFactory returnValue; 499 if (RESOURCE_LOCAL.equals(persistenceUnitInfo.getTransactionType())) { 500 returnValue = getOrCreateEntityManagerFactory(emfs, null, this.name); 501 } else { 502 returnValue = getOrCreateEntityManagerFactory(emfs, this.persistenceUnitInfo, this.name); 503 } 504 return returnValue; 505 } 506 507 @Override 508 public final void release() { 509 final EntityManagerFactory emf = this.emfs.remove(this.name); 510 if (emf != null && emf.isOpen()) { 511 emf.close(); 512 } 513 } 514 } 515 516 private final class EntityManagerResourceReference implements ResourceReference<EntityManager> { 517 518 private final String name; 519 520 private final SynchronizationType synchronizationType; 521 522 private final PersistenceUnitInfo persistenceUnitInfo; 523 524 // @GuardedBy("this") 525 private EntityManager em; 526 527 private final Future<EntityManagerFactory> emfFuture; 528 529 private final Supplier<EntityManager> emSupplier; 530 531 private EntityManagerResourceReference(final String name, 532 final SynchronizationType synchronizationType) { 533 super(); 534 this.name = Objects.requireNonNull(name); 535 this.synchronizationType = Objects.requireNonNull(synchronizationType); 536 this.persistenceUnitInfo = getPersistenceUnitInfo(name); 537 assert this.persistenceUnitInfo != null; 538 final ExecutorService taskExecutorService = ((WeldManager)CDI.current().getBeanManager()).getServices().get(ExecutorServices.class).getTaskExecutor(); 539 assert taskExecutorService != null; 540 if (this.isResourceLocal()) { 541 // Kick off the lengthy process of setting up an 542 // EntityManagerFactory in the background with the optimistic 543 // assumption, possibly incorrect, that someone will call 544 // getInstance() at some point. 545 this.emfFuture = taskExecutorService.submit(() -> { 546 return getOrCreateEntityManagerFactory(emfs, null, this.name); 547 }); 548 this.emSupplier = () -> { 549 try { 550 return emfFuture.get().createEntityManager(); 551 } catch (final ExecutionException executionException) { 552 throw new RuntimeException(executionException.getMessage(), executionException); 553 } catch (final InterruptedException interruptedException) { 554 Thread.currentThread().interrupt(); 555 throw new RuntimeException(interruptedException.getMessage(), interruptedException); 556 } 557 }; 558 } else { 559 // Kick off the lengthy process of setting up an 560 // EntityManagerFactory in the background with the optimistic 561 // assumption, possibly incorrect, that someone will call 562 // getInstance() at some point. 563 this.emfFuture = taskExecutorService.submit(() -> { 564 return getOrCreateEntityManagerFactory(emfs, this.persistenceUnitInfo, this.name); 565 }); 566 this.emSupplier = () -> { 567 try { 568 final EntityManager em = emfFuture.get().createEntityManager(this.synchronizationType); 569 JpaInjectionServices.this.ems.add(em); 570 return em; 571 } catch (final ExecutionException executionException) { 572 throw new RuntimeException(executionException.getMessage(), executionException); 573 } catch (final InterruptedException interruptedException) { 574 Thread.currentThread().interrupt(); 575 throw new RuntimeException(interruptedException.getMessage(), interruptedException); 576 } 577 }; 578 } 579 580 581 } 582 583 private final boolean isResourceLocal() { 584 return RESOURCE_LOCAL.equals(this.persistenceUnitInfo.getTransactionType()); 585 } 586 587 @Override 588 public synchronized final EntityManager getInstance() { 589 if (this.em == null) { 590 this.em = this.emSupplier.get(); 591 } 592 return this.em; 593 } 594 595 @Override 596 public final void release() { 597 final EntityManager em; 598 synchronized (this) { 599 em = this.em; 600 this.em = null; 601 } 602 if (em != null) { 603 if (em.isOpen() && this.isResourceLocal()) { 604 // Note that according to the javadocs on 605 // EntityManager#close(), you're never supposed to call 606 // EntityManager#close() on a container-managed 607 // EntityManager; hence the isResourceLocal() check here. 608 em.close(); 609 } 610 JpaInjectionServices.this.ems.remove(em); 611 } 612 if (!this.emfFuture.isDone()) { 613 this.emfFuture.cancel(true); 614 } 615 } 616 617 } 618 619}