001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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 implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.jbi.nmr.flow.jca;
018    
019    import java.io.Serializable;
020    import java.util.Map;
021    import java.util.Set;
022    import java.util.Timer;
023    import java.util.concurrent.ConcurrentHashMap;
024    import java.util.concurrent.CopyOnWriteArraySet;
025    import java.util.concurrent.atomic.AtomicBoolean;
026    
027    import javax.jbi.JBIException;
028    import javax.jbi.messaging.MessageExchange;
029    import javax.jbi.messaging.MessageExchange.Role;
030    import javax.jbi.messaging.MessagingException;
031    import javax.jbi.servicedesc.ServiceEndpoint;
032    import javax.jms.Connection;
033    import javax.jms.ConnectionFactory;
034    import javax.jms.DeliveryMode;
035    import javax.jms.Destination;
036    import javax.jms.JMSException;
037    import javax.jms.Message;
038    import javax.jms.MessageListener;
039    import javax.jms.MessageProducer;
040    import javax.jms.ObjectMessage;
041    import javax.jms.Session;
042    import javax.resource.ResourceException;
043    import javax.resource.spi.BootstrapContext;
044    import javax.resource.spi.ConnectionManager;
045    import javax.resource.spi.UnavailableException;
046    import javax.resource.spi.XATerminator;
047    import javax.resource.spi.endpoint.MessageEndpointFactory;
048    import javax.resource.spi.work.WorkManager;
049    import javax.transaction.HeuristicMixedException;
050    import javax.transaction.HeuristicRollbackException;
051    import javax.transaction.InvalidTransactionException;
052    import javax.transaction.NotSupportedException;
053    import javax.transaction.RollbackException;
054    import javax.transaction.Status;
055    import javax.transaction.SystemException;
056    import javax.transaction.Transaction;
057    import javax.transaction.TransactionManager;
058    
059    import org.apache.activemq.ActiveMQConnectionFactory;
060    import org.apache.activemq.advisory.AdvisorySupport;
061    import org.apache.activemq.command.ActiveMQDestination;
062    import org.apache.activemq.command.ActiveMQMessage;
063    import org.apache.activemq.command.ActiveMQQueue;
064    import org.apache.activemq.command.ActiveMQTopic;
065    import org.apache.activemq.command.ConsumerId;
066    import org.apache.activemq.command.ConsumerInfo;
067    import org.apache.activemq.command.RemoveInfo;
068    import org.apache.activemq.ra.ActiveMQActivationSpec;
069    import org.apache.activemq.ra.ActiveMQManagedConnectionFactory;
070    import org.apache.activemq.ra.ActiveMQResourceAdapter;
071    import org.apache.geronimo.transaction.manager.NamedXAResource;
072    import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
073    import org.apache.servicemix.JbiConstants;
074    import org.apache.servicemix.executors.Executor;
075    import org.apache.servicemix.executors.ExecutorFactory;
076    import org.apache.servicemix.executors.WorkManagerWrapper;
077    import org.apache.servicemix.jbi.event.ComponentAdapter;
078    import org.apache.servicemix.jbi.event.ComponentEvent;
079    import org.apache.servicemix.jbi.event.ComponentListener;
080    import org.apache.servicemix.jbi.event.EndpointAdapter;
081    import org.apache.servicemix.jbi.event.EndpointEvent;
082    import org.apache.servicemix.jbi.event.EndpointListener;
083    import org.apache.servicemix.jbi.messaging.MessageExchangeImpl;
084    import org.apache.servicemix.jbi.nmr.Broker;
085    import org.apache.servicemix.jbi.nmr.flow.AbstractFlow;
086    import org.apache.servicemix.jbi.servicedesc.EndpointSupport;
087    import org.apache.servicemix.jbi.servicedesc.InternalEndpoint;
088    import org.jencks.SingletonEndpointFactory;
089    import org.jencks.factory.ConnectionManagerFactoryBean;
090    
091    /**
092     * Use for message routing among a network of containers. All
093     * routing/registration happens automatically.
094     * 
095     * @version $Revision: 681324 $
096     * @org.apache.xbean.XBean element="jcaFlow"
097     */
098    public class JCAFlow extends AbstractFlow implements MessageListener {
099    
100        private static final String INBOUND_PREFIX = "org.apache.servicemix.jca.";
101    
102        private String jmsURL = "tcp://localhost:61616";
103        private ActiveMQConnectionFactory connectionFactory;
104        private ConnectionFactory managedConnectionFactory;
105        private String broadcastDestinationName = "org.apache.servicemix.JCAFlow";
106        private ActiveMQTopic broadcastTopic;
107        private Map<String, Connector> connectorMap = new ConcurrentHashMap<String, Connector>();
108        private AtomicBoolean started = new AtomicBoolean(false);
109        private Set<String> subscriberSet = new CopyOnWriteArraySet<String>();
110        private ConnectionManager connectionManager;
111        private Connector containerConnector;
112        private Connector broadcastConnector;
113        private Connector advisoryConnector;
114        private ActiveMQTopic advisoryTopic;
115        private EndpointListener endpointListener;
116        private ComponentListener componentListener;
117    
118        public JCAFlow() {
119        }
120    
121        public JCAFlow(String jmsURL) {
122            this.jmsURL = jmsURL;
123        }
124    
125        /**
126         * The type of Flow
127         * 
128         * @return the type
129         */
130        public String getDescription() {
131            return "jca";
132        }
133    
134        /**
135         * Returns the JMS URL for this flow
136         * 
137         * @return Returns the jmsURL.
138         */
139        public String getJmsURL() {
140            return jmsURL;
141        }
142    
143        /**
144         * Sets the JMS URL for this flow
145         * 
146         * @param jmsURL
147         *            The jmsURL to set.
148         */
149        public void setJmsURL(String jmsURL) {
150            this.jmsURL = jmsURL;
151        }
152    
153        /**
154         * Returns the ConnectionFactory for this flow
155         * 
156         * @return Returns the connectionFactory.
157         */
158        public ActiveMQConnectionFactory getConnectionFactory() {
159            return connectionFactory;
160        }
161    
162        /**
163         * Sets the ConnectionFactory for this flow
164         * 
165         * @param connectionFactory
166         *            The connectionFactory to set.
167         */
168        public void setConnectionFactory(ActiveMQConnectionFactory connectionFactory) {
169            this.connectionFactory = connectionFactory;
170        }
171    
172        /**
173         * Returns the Broadcast Destination Name for this flow
174         * 
175         * @return Returns the broadcastDestinationName.
176         */
177        public String getBroadcastDestinationName() {
178            return broadcastDestinationName;
179        }
180    
181        /**
182         * Sets the Broadcast Destination Name for this flow
183         * 
184         * @param broadcastDestinationName
185         *            The broadcastDestinationName to set.
186         */
187        public void setBroadcastDestinationName(String broadcastDestinationName) {
188            this.broadcastDestinationName = broadcastDestinationName;
189        }
190    
191        public TransactionManager getTransactionManager() {
192            return (TransactionManager) broker.getContainer().getTransactionManager();
193        }
194    
195        /**
196         * Initialize the Region
197         * 
198         * @param broker
199         * @throws JBIException
200         */
201        public void init(Broker broker) throws JBIException {
202            log.debug(broker.getContainer().getName() + ": Initializing jca flow");
203            super.init(broker);
204            // Create and register endpoint listener
205            endpointListener = new EndpointAdapter() {
206                public void internalEndpointRegistered(EndpointEvent event) {
207                    onInternalEndpointRegistered(event, true);
208                }
209    
210                public void internalEndpointUnregistered(EndpointEvent event) {
211                    onInternalEndpointUnregistered(event, true);
212                }
213            };
214            broker.getContainer().addListener(endpointListener);
215            // Create and register component listener
216            componentListener = new ComponentAdapter() {
217                public void componentStarted(ComponentEvent event) {
218                    onComponentStarted(event);
219                }
220    
221                public void componentStopped(ComponentEvent event) {
222                    onComponentStopped(event);
223                }
224            };
225            broker.getContainer().addListener(componentListener);
226            try {
227                if (connectionFactory == null) {
228                    connectionFactory = new ActiveMQConnectionFactory(jmsURL);
229                }
230    
231                // Inbound connector
232                ActiveMQDestination dest = new ActiveMQQueue(INBOUND_PREFIX + broker.getContainer().getName());
233                containerConnector = new Connector(dest, this, true);
234                containerConnector.start();
235    
236                // Outbound connector
237                ActiveMQResourceAdapter outboundRa = new ActiveMQResourceAdapter();
238                outboundRa.setConnectionFactory(connectionFactory);
239                //
240                // We need to explicitly set the server url unless we use the
241                // default jms url, so set it.
242                //
243                if (outboundRa.getInfo().getServerUrl() == null) {
244                    log.info("ActiveMQResourceAdapter server url was null.  Setting it to: " + jmsURL);
245                    outboundRa.getInfo().setServerUrl(jmsURL);
246                }
247                ActiveMQManagedConnectionFactory mcf = new ActiveMQManagedConnectionFactory();
248                mcf.setResourceAdapter(outboundRa);
249                managedConnectionFactory = (ConnectionFactory) mcf.createConnectionFactory(getConnectionManager());
250    
251                // Inbound broadcast
252                broadcastTopic = new ActiveMQTopic(broadcastDestinationName);
253                advisoryTopic = AdvisorySupport.getConsumerAdvisoryTopic((ActiveMQDestination) broadcastTopic);
254            } catch (Exception e) {
255                log.error("Failed to initialize JCAFlow", e);
256                throw new JBIException(e);
257            }
258        }
259    
260        /**
261         * start the flow
262         * 
263         * @throws JBIException
264         */
265        public void start() throws JBIException {
266            if (started.compareAndSet(false, true)) {
267                super.start();
268                try {
269                    // Inbound broadcast
270                    MessageListener listener = new MessageListener() {
271                        public void onMessage(Message message) {
272                            try {
273                                Object obj = ((ObjectMessage) message).getObject();
274                                if (obj instanceof EndpointEvent) {
275                                    EndpointEvent event = (EndpointEvent) obj;
276                                    String container = ((InternalEndpoint) event.getEndpoint()).getComponentNameSpace().getContainerName();
277                                    if (!getBroker().getContainer().getName().equals(container)) {
278                                        if (event.getEventType() == EndpointEvent.INTERNAL_ENDPOINT_REGISTERED) {
279                                            onRemoteEndpointRegistered(event);
280                                        } else if (event.getEventType() == EndpointEvent.INTERNAL_ENDPOINT_UNREGISTERED) {
281                                            onRemoteEndpointUnregistered(event);
282                                        }
283                                    }
284                                }
285                            } catch (Exception e) {
286                                log.error("Error processing incoming broadcast message", e);
287                            }
288                        }
289                    };
290                    broadcastConnector = new Connector(broadcastTopic, listener, false);
291                    broadcastConnector.start();
292    
293                    listener = new MessageListener() {
294                        public void onMessage(Message message) {
295                            if (started.get()) {
296                                onAdvisoryMessage(((ActiveMQMessage) message).getDataStructure());
297                            }
298                        }
299                    };
300                    advisoryConnector = new Connector(advisoryTopic, listener, false);
301                    advisoryConnector.start();
302                } catch (Exception e) {
303                    throw new JBIException("JMSException caught in start: " + e.getMessage(), e);
304                }
305            }
306        }
307    
308        /**
309         * stop the flow
310         * 
311         * @throws JBIException
312         */
313        public void stop() throws JBIException {
314            if (started.compareAndSet(true, false)) {
315                super.stop();
316                try {
317                    broadcastConnector.stop();
318                } catch (Exception e) {
319                    log.debug("Error closing jca connector", e);
320                }
321                try {
322                    advisoryConnector.stop();
323                } catch (Exception e) {
324                    log.debug("Error closing jca connector", e);
325                }
326            }
327        }
328    
329        public void shutDown() throws JBIException {
330            super.shutDown();
331            stop();
332            // Remove endpoint listener
333            broker.getContainer().removeListener(endpointListener);
334            // Remove component listener
335            broker.getContainer().removeListener(componentListener);
336            // Destroy connectors
337            while (!connectorMap.isEmpty()) {
338                Connector connector = connectorMap.remove(connectorMap.keySet().iterator().next());
339                try {
340                    connector.stop();
341                } catch (Exception e) {
342                    log.debug("Error closing jca connector", e);
343                }
344            }
345            try {
346                containerConnector.stop();
347            } catch (Exception e) {
348                log.debug("Error closing jca connector", e);
349            }
350        }
351    
352        /**
353         * useful for testing
354         * 
355         * @return number of containers in the network
356         */
357        public int numberInNetwork() {
358            return subscriberSet.size();
359        }
360    
361        /**
362         * Check if the flow can support the requested QoS for this exchange
363         * 
364         * @param me
365         *            the exchange to check
366         * @return true if this flow can handle the given exchange
367         */
368        public boolean canHandle(MessageExchange me) {
369            if (isSynchronous(me)) {
370                return false;
371            }
372            return true;
373        }
374    
375        public void onInternalEndpointRegistered(EndpointEvent event, boolean broadcast) {
376            if (!started.get()) {
377                return;
378            }
379            try {
380                String key = EndpointSupport.getKey(event.getEndpoint());
381                if (!connectorMap.containsKey(key)) {
382                    ActiveMQDestination dest = new ActiveMQQueue(INBOUND_PREFIX + key);
383                    Connector connector = new Connector(dest, this, true);
384                    connector.start();
385                    connectorMap.put(key, connector);
386                }
387                // broadcast change to the network
388                if (broadcast) {
389                    log.debug(broker.getContainer().getName() + ": broadcasting info for " + event);
390                    sendJmsMessage(broadcastTopic, event, false, false);
391                }
392            } catch (Exception e) {
393                log.error("Cannot create consumer for " + event.getEndpoint(), e);
394            }
395        }
396    
397        public void onInternalEndpointUnregistered(EndpointEvent event, boolean broadcast) {
398            try {
399                String key = EndpointSupport.getKey(event.getEndpoint());
400                Connector connector = connectorMap.remove(key);
401                if (connector != null) {
402                    connector.stop();
403                }
404                // broadcast change to the network
405                if (broadcast) {
406                    log.debug(broker.getContainer().getName() + ": broadcasting info for " + event);
407                    sendJmsMessage(broadcastTopic, event, false, false);
408                }
409            } catch (Exception e) {
410                log.error("Cannot destroy consumer for " + event, e);
411            }
412        }
413    
414        public void onComponentStarted(ComponentEvent event) {
415            if (!started.get()) {
416                return;
417            }
418            try {
419                String key = event.getComponent().getName();
420                if (!connectorMap.containsKey(key)) {
421                    ActiveMQDestination dest = new ActiveMQQueue(INBOUND_PREFIX + key);
422                    Connector connector = new Connector(dest, this, true);
423                    connector.start();
424                    connectorMap.put(key, connector);
425                }
426            } catch (Exception e) {
427                log.error("Cannot create consumer for component " + event.getComponent().getName(), e);
428            }
429        }
430    
431        public void onComponentStopped(ComponentEvent event) {
432            try {
433                String key = event.getComponent().getName();
434                Connector connector = connectorMap.remove(key);
435                if (connector != null) {
436                    connector.stop();
437                }
438            } catch (Exception e) {
439                log.error("Cannot destroy consumer for component " + event.getComponent().getName(), e);
440            }
441        }
442    
443        public void onRemoteEndpointRegistered(EndpointEvent event) {
444            log.debug(broker.getContainer().getName() + ": adding remote endpoint: " + event.getEndpoint());
445            broker.getContainer().getRegistry().registerRemoteEndpoint(event.getEndpoint());
446        }
447    
448        public void onRemoteEndpointUnregistered(EndpointEvent event) {
449            log.debug(broker.getContainer().getName() + ": removing remote endpoint: " + event.getEndpoint());
450            broker.getContainer().getRegistry().unregisterRemoteEndpoint(event.getEndpoint());
451        }
452    
453        /**
454         * Distribute an ExchangePacket
455         * 
456         * @param me
457         * @throws MessagingException
458         */
459        protected void doSend(MessageExchangeImpl me) throws MessagingException {
460            doRouting(me);
461        }
462    
463        /**
464         * Distribute an ExchangePacket
465         * 
466         * @param me
467         * @throws MessagingException
468         */
469        public void doRouting(final MessageExchangeImpl me) throws MessagingException {
470            // let ActiveMQ do the routing ...
471            try {
472                String destination;
473                if (me.getRole() == Role.PROVIDER) {
474                    if (me.getDestinationId() == null) {
475                        destination = INBOUND_PREFIX + EndpointSupport.getKey(me.getEndpoint());
476                    } else if (Boolean.TRUE.equals(me.getProperty(JbiConstants.STATELESS_PROVIDER)) && !isSynchronous(me)) {
477                        destination = INBOUND_PREFIX + me.getDestinationId().getName();
478                    } else {
479                        destination = INBOUND_PREFIX + me.getDestinationId().getContainerName();
480                    }
481                } else {
482                    if (me.getSourceId() == null) {
483                        throw new IllegalStateException("No sourceId set on the exchange");
484                    } else if (Boolean.TRUE.equals(me.getProperty(JbiConstants.STATELESS_CONSUMER)) && !isSynchronous(me)) {
485                        // If the consumer is stateless and has specified a sender
486                        // endpoint,
487                        // this exchange will be sent to the given endpoint queue,
488                        // so that
489                        // This property must have been created using
490                        // EndpointSupport.getKey
491                        // fail-over and load-balancing can be achieved
492                        if (me.getProperty(JbiConstants.SENDER_ENDPOINT) != null) {
493                            destination = INBOUND_PREFIX + me.getProperty(JbiConstants.SENDER_ENDPOINT);
494                        } else {
495                            destination = INBOUND_PREFIX + me.getSourceId().getName();
496                        }
497                    } else {
498                        destination = INBOUND_PREFIX + me.getSourceId().getContainerName();
499                    }
500                }
501                if (me.isTransacted()) {
502                    me.setTxState(MessageExchangeImpl.TX_STATE_ENLISTED);
503                }
504                sendJmsMessage(new ActiveMQQueue(destination), me, isPersistent(me), me.isTransacted());
505            } catch (JMSException e) {
506                log.error("Failed to send exchange: " + me + " internal JMS Network", e);
507                throw new MessagingException(e);
508            } catch (SystemException e) {
509                log.error("Failed to send exchange: " + me + " transaction problem", e);
510                throw new MessagingException(e);
511            }
512        }
513    
514        /**
515         * MessageListener implementation
516         * 
517         * @param message
518         */
519        public void onMessage(Message message) {
520            try {
521                if (message != null && started.get()) {
522                    ObjectMessage objMsg = (ObjectMessage) message;
523                    final MessageExchangeImpl me = (MessageExchangeImpl) objMsg.getObject();
524                    // Hack for redelivery: AMQ is too optimized and the object is
525                    // the same upon redelivery
526                    // so that there are side effect (the exchange state may have
527                    // been modified)
528                    // See http://jira.activemq.org/jira/browse/AMQ-519
529                    // me = (MessageExchangeImpl) ((ActiveMQObjectMessage)
530                    // ((ActiveMQObjectMessage) message).copy()).getObject();
531                    TransactionManager tm = (TransactionManager) getTransactionManager();
532                    if (tm != null) {
533                        me.setTransactionContext(tm.getTransaction());
534                    }
535                    if (me.getDestinationId() == null) {
536                        ServiceEndpoint se = me.getEndpoint();
537                        se = broker.getContainer().getRegistry().getInternalEndpoint(se.getServiceName(), se.getEndpointName());
538                        me.setEndpoint(se);
539                        me.setDestinationId(((InternalEndpoint) se).getComponentNameSpace());
540                    }
541                    super.doRouting(me);
542                }
543            } catch (JMSException jmsEx) {
544                log.error("Caught an exception unpacking JMS Message: ", jmsEx);
545            } catch (MessagingException e) {
546                log.error("Caught an exception routing ExchangePacket: ", e);
547            } catch (SystemException e) {
548                log.error("Caught an exception acessing transaction context: ", e);
549            }
550        }
551    
552        protected void onAdvisoryMessage(Object obj) {
553            if (obj instanceof ConsumerInfo) {
554                ConsumerInfo info = (ConsumerInfo) obj;
555                subscriberSet.add(info.getConsumerId().getConnectionId());
556                ServiceEndpoint[] endpoints = broker.getContainer().getRegistry().getEndpointsForInterface(null);
557                for (int i = 0; i < endpoints.length; i++) {
558                    if (endpoints[i] instanceof InternalEndpoint && ((InternalEndpoint) endpoints[i]).isLocal()) {
559                        onInternalEndpointRegistered(new EndpointEvent(endpoints[i], EndpointEvent.INTERNAL_ENDPOINT_REGISTERED), true);
560                    }
561                }
562            } else if (obj instanceof RemoveInfo) {
563                ConsumerId id = (ConsumerId) ((RemoveInfo) obj).getObjectId();
564                subscriberSet.remove(id.getConnectionId());
565                removeAllPackets(id.getConnectionId());
566            }
567        }
568    
569        private void removeAllPackets(String containerName) {
570            // TODO: broker.getRegistry().unregisterRemoteEndpoints(containerName);
571        }
572    
573        public ConnectionManager getConnectionManager() throws Exception {
574            if (connectionManager == null) {
575                ConnectionManagerFactoryBean cmfb = new ConnectionManagerFactoryBean();
576                TransactionManager txmgr = (TransactionManager) broker.getContainer().getTransactionManager();
577                if (!(txmgr instanceof RecoverableTransactionManager)) {
578                    txmgr = new RecoverableTransactionManagerWrapper(txmgr);
579                }
580                cmfb.setTransactionManager((RecoverableTransactionManager) txmgr);
581                cmfb.setTransaction("xa");
582                cmfb.afterPropertiesSet();
583                connectionManager = (ConnectionManager) cmfb.getObject();
584            }
585            return connectionManager;
586        }
587    
588        public void setConnectionManager(ConnectionManager connectionManager) {
589            this.connectionManager = connectionManager;
590        }
591    
592        public String toString() {
593            return broker.getContainer().getName() + " JCAFlow";
594        }
595    
596        private void sendJmsMessage(Destination dest, Serializable object, boolean persistent, boolean transacted) throws JMSException,
597                        SystemException {
598            if (transacted) {
599                TransactionManager tm = (TransactionManager) getBroker().getContainer().getTransactionManager();
600                if (tm.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
601                    return;
602                }
603            }
604            Connection connection = managedConnectionFactory.createConnection();
605            try {
606                Session session = connection.createSession(transacted, transacted ? Session.SESSION_TRANSACTED : Session.AUTO_ACKNOWLEDGE);
607                ObjectMessage msg = session.createObjectMessage(object);
608                MessageProducer producer = session.createProducer(dest);
609                producer.setDeliveryMode(persistent ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT);
610                producer.send(msg);
611            } finally {
612                connection.close();
613            }
614        }
615    
616        class Connector {
617            private ActiveMQResourceAdapter ra;
618    
619            private MessageEndpointFactory endpointFactory;
620    
621            private ActiveMQActivationSpec spec;
622    
623            private Executor executor;
624    
625            public Connector(ActiveMQDestination destination, MessageListener listener, boolean transacted) {
626                ra = new ActiveMQResourceAdapter();
627                ra.setConnectionFactory(connectionFactory);
628                SingletonEndpointFactory ef = new SingletonEndpointFactory(listener, transacted ? getTransactionManager() : null);
629                ef.setName(INBOUND_PREFIX + broker.getContainer().getName());
630                endpointFactory = ef;
631                spec = new ActiveMQActivationSpec();
632                spec.setActiveMQDestination(destination);
633            }
634    
635            public void start() throws ResourceException {
636                ExecutorFactory factory = broker.getContainer().getExecutorFactory();
637                executor = factory.createExecutor("flow.jca." + spec.getDestination());
638                BootstrapContext context = new SimpleBootstrapContext(new WorkManagerWrapper(executor));
639                ra.start(context);
640                spec.setResourceAdapter(ra);
641                ra.endpointActivation(endpointFactory, spec);
642            }
643    
644            public void stop() {
645                ra.endpointDeactivation(endpointFactory, spec);
646                ra.stop();
647                executor.shutdown();
648            }
649        }
650    
651        class SimpleBootstrapContext implements BootstrapContext {
652            private final WorkManager workManager;
653    
654            public SimpleBootstrapContext(WorkManager workManager) {
655                this.workManager = workManager;
656            }
657    
658            public Timer createTimer() throws UnavailableException {
659                throw new UnsupportedOperationException();
660            }
661    
662            public WorkManager getWorkManager() {
663                return workManager;
664            }
665    
666            public XATerminator getXATerminator() {
667                throw new UnsupportedOperationException();
668            }
669    
670        }
671    
672        public static class RecoverableTransactionManagerWrapper implements RecoverableTransactionManager {
673            private final TransactionManager txMgr;
674    
675            public RecoverableTransactionManagerWrapper(TransactionManager txMgr) {
676                this.txMgr = txMgr;
677            }
678    
679            public void begin() throws NotSupportedException, SystemException {
680                txMgr.begin();
681            }
682    
683            public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException,
684                                        RollbackException, SecurityException, SystemException {
685                txMgr.commit();
686            }
687    
688            public int getStatus() throws SystemException {
689                return txMgr.getStatus();
690            }
691    
692            public Transaction getTransaction() throws SystemException {
693                return txMgr.getTransaction();
694            }
695    
696            public void resume(Transaction transaction) throws IllegalStateException, InvalidTransactionException, SystemException {
697                txMgr.resume(transaction);
698            }
699    
700            public void rollback() throws IllegalStateException, SecurityException, SystemException {
701                txMgr.rollback();
702            }
703    
704            public void setRollbackOnly() throws IllegalStateException, SystemException {
705                txMgr.setRollbackOnly();
706            }
707    
708            public void setTransactionTimeout(int i) throws SystemException {
709                txMgr.setTransactionTimeout(i);
710            }
711    
712            public Transaction suspend() throws SystemException {
713                return txMgr.suspend();
714            }
715    
716            public void recoveryError(Exception e) {
717                throw new UnsupportedOperationException();
718            }
719    
720            public void recoverResourceManager(NamedXAResource namedXAResource) {
721                throw new UnsupportedOperationException();
722            }
723        }
724    
725    }