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;
021import java.lang.reflect.Type;
022
023import javax.enterprise.context.Dependent;
024
025import javax.enterprise.event.Observes;
026
027import javax.enterprise.inject.spi.AfterBeanDiscovery;
028import javax.enterprise.inject.spi.BeforeBeanDiscovery;
029import javax.enterprise.inject.spi.Extension;
030import javax.enterprise.inject.spi.ProcessAnnotatedType;
031
032import javax.transaction.TransactionManager;
033import javax.transaction.TransactionSynchronizationRegistry;
034
035import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple;
036
037import com.arjuna.ats.jta.cdi.TransactionContext;
038
039/**
040 * A <a
041 * href="http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#spi">CDI 2.0
042 * portable extension</a> that adapts the <a
043 * href="https://narayana.io/">Narayana transaction engine</a> to a <a
044 * href="http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#part_2">CDI
045 * 2.0 SE environment</a>.
046 *
047 * @author <a href="https://about.me/lairdnelson"
048 * target="_parent">Laird Nelson</a>
049 */
050public final class NarayanaExtension implements Extension {
051
052  
053  /*
054   * Constructors.
055   */
056
057
058  /**
059   * Creates a new {@link NarayanaExtension}.
060   */
061  public NarayanaExtension() {
062    super();
063  }
064
065
066  /*
067   * Instance methods.
068   */
069
070  
071  private final void beforeBeanDiscovery(@Observes final BeforeBeanDiscovery event) throws ReflectiveOperationException {
072
073    // Hack the TransactionContext class to not require JNDI.
074    
075    Field field = TransactionContext.class.getDeclaredField("transactionManager");
076    assert field != null;
077    assert Modifier.isStatic(field.getModifiers());
078    assert Modifier.isPrivate(field.getModifiers());
079    assert TransactionManager.class.equals(field.getType());
080    field.setAccessible(true);
081    field.set(null, com.arjuna.ats.jta.TransactionManager.transactionManager());
082
083    field = TransactionContext.class.getDeclaredField("transactionSynchronizationRegistry");
084    assert field != null;
085    assert Modifier.isStatic(field.getModifiers());
086    assert Modifier.isPrivate(field.getModifiers());
087    assert TransactionSynchronizationRegistry.class.equals(field.getType());
088    field.setAccessible(true);
089    field.set(null, new TransactionSynchronizationRegistryImple());
090  }
091  
092  private final void afterBeanDiscovery(@Observes final AfterBeanDiscovery event) {
093    // A note on the scope for this bean:
094    // com.arjuna.ats.jta.TransactionManager#transactionManager()
095    // returns a singleton.  But I suppose there is no guarantee in
096    // the future that it will do so, and in any event the rest of the
097    // Narayana infrastructure doesn't always cache the return value
098    // of this method, so we want to preserve those semantics.
099    if (event != null) {
100      event.addBean()
101        .types(TransactionManager.class)
102        .scope(Dependent.class)
103        .createWith(cc -> com.arjuna.ats.jta.TransactionManager.transactionManager());
104    }
105  }
106
107  private final void onTypeDiscovery(@Observes final ProcessAnnotatedType<? extends com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptorBase> event) {
108    if (event != null &&
109        event.getAnnotatedType().getBaseType().getTypeName().startsWith("com.arjuna.ats.jta.cdi.transactional.TransactionalInterceptor")) {
110      // Programmatically remove the interceptors installed by the
111      // stock Narayana extension.  These will trigger a JNDI
112      // lookup which will fail, since we are not guaranteed the
113      // presence of a JNDI implementation in the microBean
114      // environment.  We replace them with similar almost-clones
115      // present in this package that bypass the JNDI machinery.
116      // See also: https://github.com/jbosstm/narayana/pull/1344
117      event.veto();
118    }
119  }
120  
121}