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