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