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