All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.internal.sessions.coordination.jms.JMSTopicRemoteConnection Maven / Gradle / Ivy

There is a newer version: 4.0.2
Show newest version
/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
//     cdelahun - Bug 214534: added JMS Cache Coordination for publishing only
package org.eclipse.persistence.internal.sessions.coordination.jms;

import java.io.Serializable;

import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.coordination.broadcast.BroadcastRemoteConnection;
import org.eclipse.persistence.sessions.coordination.RemoteCommandManager;
import org.eclipse.persistence.sessions.coordination.jms.JMSTopicTransportManager;
import org.eclipse.persistence.sessions.serializers.JavaSerializer;
import org.eclipse.persistence.sessions.serializers.Serializer;
import org.eclipse.persistence.exceptions.RemoteCommandManagerException;

import javax.jms.BytesMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.Message;

/**
 * 

* Purpose: Define the implementation of the abstract RemoteConnection for JMS. *

* Description: Executing commands implementation of RemoteConnection is done via JMS Publisher. * * Using a single TopicConnection for both publishing and subscribing would * allow subscriber to ignore messages sent through the same TopicConnection - and therefore * allow JMSTopicRemoteConnection to ignore messages that it has itself published. * Unfortunately J2EE doesn't allow that: * J2EE spec. (J2EE.6.6 in v1.4) states: * "The following methods may only be used by application components executing * in the application client container: javax.jms.Session method setMessageListener ... * Application components in the web and EJB containers must not attempt to create more than one * active (not closed) Session object per connection." * Because of these restrictions * a) two JMSTopicRemoteConnection are required - one for publishing (external) and another one for listening (local); * b) listening should be done using subscriber.receive() in an infinite loop in a separate thread, * that's why the class implements Runnable interface. * c) publishing connection (external) could be used concurrently to send messages, so it cannot use the same publisher/session/topicConnection * Instead, it will store the TopicConnectionFactory and use it to create connections when executeCommandInternal is called (much like * DatabaseAccessor when an external pool is used) * * @author Steven Vo * @since OracleAS TopLink 10g (10.0.3) */ public class JMSTopicRemoteConnection extends BroadcastRemoteConnection implements Runnable { protected TopicConnectionFactory topicConnectionFactory; protected Topic topic; // indicates whether it's a local connection. protected boolean isLocal; // not used by External (publishing) connection unless it is set to reuse the JMSTopicPublisher private TopicPublisher publisher; protected TopicConnection topicConnection; protected TopicSession topicSession; // Used only by local (listening) connection protected TopicSubscriber subscriber; // Used only by local (listening) connection and // only in case shouldRemoveConnectionOnError==false. // Time to wait after error receiving jms message: // wait to avoid a busy thread throwing barrage of exceptions // in case JMS server goes down. // User can avoid the wait by handling ERROR_RECEIVING_JMS_MESSAGE exception. public static long WAIT_ON_ERROR_RECEIVING_JMS_MESSAGE = 10000; /** * INTERNAL: * Constructor creating either a local or external connection. Local connections created this way connect to the topicSession and cache * the session and subscriber. External connections cache only the topicConnection and will obtain the session/publisher when needed. * @param rcm */ public JMSTopicRemoteConnection(RemoteCommandManager rcm, TopicConnectionFactory topicConnectionFactory, Topic topic, boolean isLocalConnectionBeingCreated, boolean reuseJMSTopicPublisher) throws JMSException { super(rcm); this.topicConnectionFactory = topicConnectionFactory; this.topic = topic; this.isLocal = isLocalConnectionBeingCreated; rcm.logDebug("creating_broadcast_connection", getInfo()); try { if(isLocalConnectionBeingCreated) { // it's a local connection this.topicConnection = topicConnectionFactory.createTopicConnection(); this.topicSession = topicConnection.createTopicSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE); this.subscriber = topicSession.createSubscriber(topic); topicConnection.start(); rcm.logDebug("broadcast_connection_created", getInfo()); rcm.getServerPlatform().launchContainerRunnable(this); } else if (reuseJMSTopicPublisher) { // it's an external connection and is set to reuse the TopicPublisher (legacy) this.topicConnection = topicConnectionFactory.createTopicConnection(); this.topicSession = topicConnection.createTopicSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE); this.setPublisher(topicSession.createPublisher(topic)); rcm.logDebug("broadcast_connection_created", getInfo()); } //else bug214534: it's an external connection, with TopicConnections, sessions and publishers created as needed } catch (JMSException ex) { rcm.logDebug("failed_to_create_broadcast_connection", getInfo()); close(); throw ex; } } /** * Creates local connections that do not use a TopicConnection or TopicSession, * useful only for processing already received JMS messages * @param rcm * @see onMessage */ public JMSTopicRemoteConnection(RemoteCommandManager rcm){ super(rcm); this.isLocal = true; } /** * INTERNAL: * Indicates whether connection is local (subscriber) * or external (publisher). */ public boolean isLocal() { return isLocal; } /** * INTERNAL: * Execute the remote command. The result of execution is returned. * This method is used only by external (publishing) connection. */ @Override protected Object executeCommandInternal(Object command) throws Exception { TopicConnection jmsConnection = null; try { TopicPublisher topicPublisher = this.publisher; TopicSession publishingSession = this.topicSession; //if the publisher is set, reuse it. Otherwise, create it (and the connection and session) from the topicConnectionFactory if ( topicPublisher == null ){ jmsConnection = topicConnectionFactory.createTopicConnection(); publishingSession = jmsConnection.createTopicSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE); topicPublisher = publishingSession.createPublisher(topic); } Message message; if (command instanceof byte[]) { message = publishingSession.createBytesMessage(); ((BytesMessage)message).writeBytes((byte[])command); } else { message = publishingSession.createObjectMessage(); ((ObjectMessage)message).setObject((Serializable)command); } Object[] debugInfo = null; if (rcm.shouldLogDebugMessage()) { // null passed because JMSMessageId is not yet created. debugInfo = logDebugBeforePublish(null); } topicPublisher.publish(message); // debug logging is on if (debugInfo != null) { // now messageId has been created - let's use it. logDebugAfterPublish(debugInfo, message.getJMSMessageID()); } return null; } finally { //only need to close the topicConnection, not the session or publisher, and only if it was created in this method. if (jmsConnection != null) { jmsConnection.close(); } } } /** * INTERNAL: * Process received JMS message. * This method is used only by local (listening) connection. */ public void onMessage(Message message) { String topic = null; String messageId = ""; if (rcm.shouldLogDebugMessage()) { try { messageId = message.getJMSMessageID(); logDebugOnReceiveMessage(messageId); topic = logDebugJMSTopic(message); } catch (JMSException ex) { // ignore } } Object object = null; try { if (message instanceof ObjectMessage) { object = ((ObjectMessage)message).getObject(); } else if (message instanceof BytesMessage) { BytesMessage byteMessage = (BytesMessage)message; byte[] bytes = new byte[(int)byteMessage.getBodyLength()]; byteMessage.readBytes(bytes); Serializer serializer = this.rcm.getSerializer(); if (serializer == null) { serializer = JavaSerializer.instance; } object = serializer.deserialize(bytes, (AbstractSession)this.rcm.getCommandProcessor()); } else { if (this.rcm.shouldLogWarningMessage() && (topic == null)) { try { topic = ((Topic)message.getJMSDestination()).getTopicName(); } catch (JMSException ex) { // ignore topic = ""; } Object[] args = { message.getClass().getName(), topic }; this.rcm.logWarningWithoutLevelCheck("received_unexpected_message_type", args); } return; } } catch (Exception exception) { if (messageId.length() == 0) { try { messageId = message.getJMSMessageID(); } catch (JMSException ex) { // ignore } } failDeserializeMessage(messageId, exception); return; } processReceivedObject(object, messageId); } /** * INTERNAL: * Indicates whether all the resources used by connection are freed after close method returns. * Usually that's the case. However in case of local (listening) JMSTopicRemoteConnection * close merely indicates to the listening thread that it should free TopicConnection and exit. * Note that it may take a while: the listening thread waits until subscriber.receive method either * returns a message or throws an exception. */ protected boolean areAllResourcesFreedOnClose() { return !isLocal(); } /** * INTERNAL: * This method is called by close method. * This method usually * (but not always see comment to areAllResourcesFreedOnClose method) * frees all the resources. */ protected void closeInternal() throws JMSException { //this method should be a no-op now that external connections open/close TopicConnection when needed. Close on Local //connections will eventually cause topicConnection.close() in their listening thread, so it should not be called here if(areAllResourcesFreedOnClose() && topicConnection!=null) { // There is no need to close the sessions, producers, and consumers of a closed TopicConnection. topicConnection.close(); } } /** * INTERNAL: */ protected String logDebugJMSTopic(Message message) throws JMSException { String topic = ((Topic)message.getJMSDestination()).getTopicName(); Object[] args = { topic }; // call logDebugWithoutLevelCheck to avoid the second rcm.shouldLogDebugMessage() check rcm.logDebugWithoutLevelCheck("retreived_remote_message_from_JMS_topic", args); return topic; } /** * INTERNAL: * This method is used by local (listening) connection only. * The only way to exit the loop is to set isActive to false - * there should be no uncaught exceptions thrown from inside the loop. * The execution exits the loop either in case of exception in remove connection on error mode; * or by trasportManager.removeLocalConnection() call * (which calls connection.close(), which sets isActive to false). */ public void run() { JMSTopicTransportManager tm = (JMSTopicTransportManager)rcm.getTransportManager(); rcm.logDebug("broadcast_connection_start_listening", getInfo()); // indicates whether to create a new connection before exiting the thread. boolean shouldReconnect = false; // exception indicating that the received message is null. RuntimeException messageIsNullException = null; // isActive() returning false indicates that close method has been called. // Should never exit this loop because of an uncaught exception. while (isActive()) { try { Message message = subscriber.receive(); // need the second isActive check here: // close method could have been called while subscriber.receive() was waiting. if (isActive()) { if(message == null) { try { // user has a chance to handle exception - for instance to ignore it. rcm.handleException(RemoteCommandManagerException.errorJMSMessageIsNull()); // exception has been handled, go to the next iteration. continue; } catch (RuntimeException ex) { messageIsNullException = ex; // throw a dummy JMSException to get into catch block throw new JMSException(""); } } // process the message and log a warning without throwing exception if there was exception. rcm.getServerPlatform().launchContainerRunnable(new JMSOnMessageHelper(message)); } } catch (JMSException e) { // need the second isActive check here: // close method could have been called while subscriber.receive() was waiting. if (isActive()) { RemoteCommandManagerException rcmException; if(messageIsNullException != null) { rcmException = RemoteCommandManagerException.errorReceivingJMSMessage(messageIsNullException); messageIsNullException = null; } else { rcmException = RemoteCommandManagerException.errorReceivingJMSMessage(e); } if (tm.shouldRemoveConnectionOnError()) { shouldReconnect = true; Object[] args = { getServiceId(), rcmException }; rcm.logWarning("drop_connection_on_error", args); // after connection is closed isActive will return false. tm.removeLocalConnection(); } else { try { // user has a chance to handle exception: // for instance to shut down the command manager. rcm.handleException(rcmException); } catch (RuntimeException ex) { // Ignore the exception, sleep before going back to listening. Object[] args = { toString(), rcmException, WAIT_ON_ERROR_RECEIVING_JMS_MESSAGE }; rcm.logWarning("broadcast_listening_sleep_on_error", args); try { Thread.sleep(WAIT_ON_ERROR_RECEIVING_JMS_MESSAGE); } catch (InterruptedException interruptedEception) { // Ignore } } } } } } // Out of the loop - that means close method has been called. rcm.logDebug("broadcast_connection_stop_listening", getInfo()); if(isClosing()) { try { // There is no need to close the sessions, producers, and consumers of a closed TopicConnection. topicConnection.close(); } catch (JMSException closeException) { Object[] args = { displayString, closeException }; rcm.logWarning("broadcast_exception_thrown_when_attempting_to_close_connection", args); } finally { rcm.logDebug("broadcast_connection_closed", getInfo()); state = STATE_CLOSED; } } if(shouldReconnect && !tm.getRemoteCommandManager().isStopped()) { try { tm.createLocalConnection(); } catch (RemoteCommandManagerException ex) { // Ignore exception - user had a chance to handle it in createLocalConnection method: // for instance to change host url and create a new local connection. } } } /** * INTERNAL: * Used for debug logging */ protected void createDisplayString() { super.createDisplayString(); displayString = (isLocal() ? "Local " : "External ") + displayString; } /** * INTERNAL: * Return whether a BroadcastConnection should check a ServiceId against its * own ServiceId to avoid the processing of Commands with the same ServiceId. * This should take place (return true) for a JMSTopicRemoteConnection. * @return boolean */ protected boolean shouldCheckServiceId() { return true; } /** * INTERNAL: * set the TopicPublisher to be used when this RemoteConnection executes a command. * Setting the TopicPublisher avoids having it obtained on each executeCommandInternal * call. Passing in a publisher requires a TopicSession to also be set. These will * not be closed until the external RemoteConnection is closed, and then only if the * TopicConnection is also set. */ public void setPublisher(TopicPublisher publisher) { this.publisher = publisher; } public TopicPublisher getPublisher() { return publisher; } /** * INTERNAL: * set the TopicSubscriber on a local RemoteConnection for reading JMS messages when * this runnable connection is started in a thread. If setting this, a TopicConnection * is also required to be set, in order for it to be closed when the thread completes. * This is only to be used when using the JMSTopicRemoteConnection(rcm) constructor. */ public void setSuscriber(TopicSubscriber subscriber) { this.subscriber = subscriber; } public TopicSubscriber getSubscriber() { return subscriber; } /** * INTERNAL: * set the TopicSession to be used when this RemoteConnection executes a command if * the publisher is also set. Setting the TopicSession and Publisher avoids having * them obtained on each executeCommandInternal call. Passing in a TopicSession * requires a TopicPublisher to also be set. These will not be closed until the * external RemoteConnection is closed, and then only if the TopicConnection is also * set. */ public void setTopicSession(TopicSession topicSession) { this.topicSession = topicSession; } public TopicSession getTopicSession() { return topicSession; } /** * INTERNAL: * Set the TopicConnectionFactory, which is used if the publisher is not set to * obtain the TopicConnection, TopicSession and TopicPublisher */ public void setTopicConnectionFactory(TopicConnectionFactory topicConnectionFactory){ this.topicConnectionFactory = topicConnectionFactory; } public TopicConnection getTopicConnectionFactory() { return topicConnection; } /** * INTERNAL: * Set the TopicConnection. If this is set, a Publisher and TopicSession must also * be set, or a new TopicConnection will be obtained on each executeCommandInternal * call. This TopicConnection is only used on close, as closing the TopicConnection * also closes any open TopicSessions and Publishers obtained from it. */ public void setTopicConnection(TopicConnection topicConnection){ this.topicConnection = topicConnection; } public TopicConnection getTopicConnection() { return topicConnection; } /** * INTERNAL: * Set the Topic. The Topic is required with the TopicConnectionFactory to obtain connections * if the TopicPublisher is not set. * * @see */ public void setTopic(Topic topic){ this.topic = topic; } public Topic getTopic() { return topic; } class JMSOnMessageHelper implements Runnable { Message message = null; public JMSOnMessageHelper(Message message) { this.message = message; } public void run() { onMessage(message); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy