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