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