001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2018 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.narayana.jta.cdi;
018
019import java.lang.reflect.Field;
020import java.lang.reflect.Modifier;
021
022import javax.enterprise.context.ApplicationScoped;
023
024import javax.enterprise.event.Observes;
025
026import javax.enterprise.inject.CreationException;
027
028import javax.enterprise.inject.spi.AfterBeanDiscovery;
029import javax.enterprise.inject.spi.AfterDeploymentValidation;
030import javax.enterprise.inject.spi.Bean;
031import javax.enterprise.inject.spi.BeanManager;
032import javax.enterprise.inject.spi.CDI;
033import javax.enterprise.inject.spi.Extension;
034import javax.enterprise.inject.spi.ProcessAnnotatedType;
035
036import javax.transaction.SystemException;
037import javax.transaction.Transaction;
038import javax.transaction.TransactionManager;
039import javax.transaction.TransactionScoped;
040import javax.transaction.TransactionSynchronizationRegistry;
041
042import com.arjuna.ats.jta.cdi.TransactionContext;
043
044import com.arjuna.ats.jta.common.jtaPropertyManager;
045
046/**
047 * A <a
048 * href="http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi">CDI 2.0
049 * portable extension</a> that adapts the <a
050 * href="https://narayana.io/">Narayana transaction engine</a> to a <a
051 * href="http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#part_2">CDI
052 * 2.0 SE environment</a>.
053 *
054 * @author <a href="https://about.me/lairdnelson"
055 * target="_parent">Laird Nelson</a>
056 */
057public final class NarayanaExtension implements Extension {
058
059
060  /*
061   * Constructors.
062   */
063
064
065  /**
066   * Creates a new {@link NarayanaExtension}.
067   */
068  public NarayanaExtension() {
069    super();
070  }
071
072
073  /*
074   * Instance methods.
075   */
076
077
078  private final void afterBeanDiscovery(@Observes final AfterBeanDiscovery event) {
079    if (event != null) {
080
081      event.addBean()
082        .types(TransactionManager.class)
083        .scope(ApplicationScoped.class)
084        .createWith(cc -> com.arjuna.ats.jta.TransactionManager.transactionManager());
085
086      event.addBean()
087        .types(Transaction.class)
088        .scope(TransactionScoped.class)
089        .createWith(cc -> {
090            try {
091              return CDI.current().select(TransactionManager.class).get().getTransaction();
092            } catch (final SystemException systemException) {
093              throw new CreationException(systemException.getMessage(), systemException);
094            }
095          });
096
097      event.addBean()
098        .types(TransactionSynchronizationRegistry.class)
099        .scope(ApplicationScoped.class)
100        .createWith(cc -> jtaPropertyManager.getJTAEnvironmentBean().getTransactionSynchronizationRegistry());
101
102    }
103  }
104
105  private final void onTypeDiscovery(@Observes final ProcessAnnotatedType<? extends com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase> event) {
106    if (event != null &&
107        event.getAnnotatedType().getBaseType().getTypeName().startsWith("com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptor")) {
108      // Programmatically remove the interceptors installed by the
109      // stock Narayana extension.  These will trigger a JNDI
110      // lookup which will fail, since we are not guaranteed the
111      // presence of a JNDI implementation in the microBean
112      // environment.  We replace them with similar almost-clones
113      // present in this package that bypass JNDI machinery.
114      // See also: https://github.com/jbosstm/narayana/pull/1344
115      event.veto();
116    }
117  }
118
119  private final void afterDeploymentValidation(@Observes final AfterDeploymentValidation event, final BeanManager beanManager) throws ReflectiveOperationException {
120
121    if (event != null && beanManager != null) {
122
123      final Bean<?> transactionManagerBean =
124        beanManager.resolve(beanManager.getBeans(TransactionManager.class));
125      assert transactionManagerBean != null;
126      final TransactionManager transactionManager =
127        (TransactionManager)beanManager.getReference(transactionManagerBean,
128                                                     TransactionManager.class,
129                                                     beanManager.createCreationalContext(transactionManagerBean));
130      assert transactionManager != null;
131
132      final Bean<?> transactionSynchronizationRegistryBean = beanManager.resolve(beanManager.getBeans(TransactionSynchronizationRegistry.class));
133      assert transactionSynchronizationRegistryBean != null;
134      final TransactionSynchronizationRegistry transactionSynchronizationRegistry =
135        (TransactionSynchronizationRegistry)beanManager.getReference(transactionSynchronizationRegistryBean,
136                                                                     TransactionSynchronizationRegistry.class,
137                                                                     beanManager.createCreationalContext(transactionSynchronizationRegistryBean));
138      assert transactionSynchronizationRegistry != null;
139
140      // Hack the TransactionContext class to not require JNDI.
141
142      Field field = TransactionContext.class.getDeclaredField("transactionManager");
143      assert field != null;
144      assert Modifier.isStatic(field.getModifiers());
145      assert Modifier.isPrivate(field.getModifiers());
146      assert TransactionManager.class.equals(field.getType());
147      field.setAccessible(true);
148      field.set(null, transactionManager);
149
150      field = TransactionContext.class.getDeclaredField("transactionSynchronizationRegistry");
151      assert field != null;
152      assert Modifier.isStatic(field.getModifiers());
153      assert Modifier.isPrivate(field.getModifiers());
154      assert TransactionSynchronizationRegistry.class.equals(field.getType());
155      field.setAccessible(true);
156      field.set(null, transactionSynchronizationRegistry);
157
158    }
159
160  }
161
162}