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}