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