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