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}