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