blob: 1d5e12e4fec1829d910fdfe26f555cf9ffe7eb9d [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 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:
// cdelahun - Bug 214534: added JMS Cache Coordination for publishing only
package org.eclipse.persistence.sessions.coordination.jms;
import java.util.Map;
import jakarta.jms.Topic;
import jakarta.jms.TopicConnectionFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import org.eclipse.persistence.exceptions.RemoteCommandManagerException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.sessions.coordination.RemoteConnection;
import org.eclipse.persistence.internal.sessions.coordination.jms.JMSTopicRemoteConnection;
import org.eclipse.persistence.sessions.coordination.broadcast.BroadcastTransportManager;
import org.eclipse.persistence.sessions.coordination.RemoteCommandManager;
import org.eclipse.persistence.sessions.coordination.TransportManager;
/**
* <p>
* <b>Purpose</b>: Provide a transport implementation for the Remote Command Module (RCM) that publishes
* to a JMS topic.
* </p><p>
* <b>Description</b>: This class manages two connections: an external connection for publishing to JMS,
* and a local connection which can be used to process JMS messages received from an application JMS listener.
* </p>
* @author Chris Delahunt
* @since EclipseLink 2.1
*/
public class JMSPublishingTransportManager extends BroadcastTransportManager {
protected String connectionFactoryName;
/*
* flag used to toggle between spec compliant behavior and legacy behavior. Default false causes external JMSTopicRemoteConnection to cache the
* TopicConnectionFactory and obtain TopicConnections etc only when executeCommand is called, releasing them immediately.
* True causes TopicConnection, TopicSession and TopicPublisher to be held and used for every executeCommand call.
* A value of true has spec implications, as TopicPublishers are not concurrent objects, but was added in for applications
* that may have worked previously but encounter performance issues with repeatedly creating TopicConnections etc.
*/
protected boolean reuseJMSTopicPublisher = false;
public static final String DEFAULT_TOPIC = "jms/EclipseLinkTopic";
public static final String DEFAULT_CONNECTION_FACTORY = "jms/EclipseLinkTopicConnectionFactory";
/**
* PUBLIC:
* Creates a JMSPublishingOnlyTopicTransportManager
*/
public JMSPublishingTransportManager(RemoteCommandManager rcm) {
super(rcm);
}
/**
* INTERNAL:
* This method creates JMSTopicRemoteConnection to be used by this TransportManager.
* Don't confuse this method with no-op createConnection(ServiceId serviceId).
*/
protected JMSTopicRemoteConnection createConnection(boolean isLocalConnectionBeingCreated) throws RemoteCommandManagerException {
Context remoteHostContext = null;
try {
remoteHostContext = getRemoteHostContext(getTopicHostUrl());
TopicConnectionFactory connectionFactory = getTopicConnectionFactory(remoteHostContext);
Topic topic = getTopic(remoteHostContext);
// external connection is a publisher; local connection is a subscriber
return new JMSTopicRemoteConnection(rcm, connectionFactory, topic, isLocalConnectionBeingCreated, reuseJMSTopicPublisher);
} catch (Exception ex) {
RemoteCommandManagerException rcmException;
if(isLocalConnectionBeingCreated) {
rcmException = RemoteCommandManagerException.errorCreatingLocalJMSConnection(topicName, connectionFactoryName, ex);
} else {
rcmException = RemoteCommandManagerException.errorCreatingJMSConnection(topicName, connectionFactoryName, ex);
}
throw rcmException;
} finally {
if(remoteHostContext != null) {
try {
remoteHostContext.close();
} catch (NamingException namingException) {
// ignore
}
}
}
}
/**
* INTERNAL:
* JMSTopicTransportManager doesn't use DiscoveryManager, therefore
* this method is called during RCM initialization to create all the necessary connections.
*/
@Override
public void createConnections() {
createExternalConnection();
createLocalConnection();
}
/**
* INTERNAL:
* JMSPublishingTransportManager has maximum one external connection.
* Verify there are no external connections,
* create a new external connection,
* add it to external connections' map.
*/
public void createExternalConnection() {
synchronized(connectionsToExternalServices) {
if(connectionsToExternalServices.isEmpty()) {
try {
connectionsToExternalServices.put(rcm.getServiceId().getId(), createConnection(false));
} catch (RemoteCommandManagerException rcmException) {
// to recover handle RemoteCommandManagerException.ERROR_CREATING_JMS_CONNECTION:
// after changing something (for instance jmsHostUrl)
// call createExternalConnection method again.
rcm.handleException(rcmException);
}
}
}
}
/**
* INTERNAL:
* JMSPublishingTransportManager has only two connections: one local and one external.
* In case the local connection doesn't exist, this method creates it and holds it to be used
* when processing incoming JMS messages. The stored local connection on JMSPublishingTransportManager
* does not connect to topicConnection, and instead must be used from an MDB when a message is received
*/
@Override
public void createLocalConnection() {
if(localConnection == null) {
localConnection = new JMSTopicRemoteConnection(rcm);
}
}
/**
* INTERNAL:
* In case there's no external connection attempts to create one.
* Returns clone of the original map.
*/
@Override
public Map<String, RemoteConnection> getConnectionsToExternalServicesForCommandPropagation() {
if(this.getConnectionsToExternalServices().isEmpty() && !this.rcm.isStopped()) {
this.createExternalConnection();
}
return super.getConnectionsToExternalServicesForCommandPropagation();
}
/**
* PUBLIC:
* flag used to toggle between j2EE/JMS spec compliant behavior and legacy behavior. Default value false causes external
* JMSTopicRemoteConnection to cache the TopicConnectionFactory and obtain TopicConnections, TopicSession and TopicPublishers
* every time executeCommand is called, and then closing them immediately. This is JMS and J2EE compliant, as the TopicConnection
* is never reused in different threads.
* True causes TopicConnection, TopicSession and TopicPublisher to be cached within the JMSTopicRemoteConnection and used for
* every executeCommand call. These objects can potentially used concurrently, which the JMS spec does not force
* providers to support.
*/
public boolean getReuseJMSTopicPublisher(){
return this.reuseJMSTopicPublisher;
}
/**
* INTERNAL:
*/
protected Topic getTopic(Context remoteHostContext) {
try {
return (Topic)remoteHostContext.lookup(topicName);
} catch (NamingException e) {
RemoteCommandManagerException rcmException = RemoteCommandManagerException.errorLookingUpRemoteConnection(topicName, rcm.getUrl(), e);
rcm.handleException(rcmException);
// If the handler hasn't thrown the exception rethrow it here - it's impossible to recover.
throw rcmException;
}
}
/**
* INTERNAL:
*/
protected TopicConnectionFactory getTopicConnectionFactory(Context remoteHostContext) {
try {
return (TopicConnectionFactory)remoteHostContext.lookup(connectionFactoryName);
} catch (NamingException e) {
RemoteCommandManagerException rcmException = RemoteCommandManagerException.errorLookingUpRemoteConnection(connectionFactoryName, rcm.getUrl(), e);
rcm.handleException(rcmException);
// If the handler hasn't thrown the exception rethrow it here - it's impossible to recover.
throw rcmException;
}
}
/**
* PUBLIC:
* Return the JMS Topic Connection Factory Name for the JMS Topic connections.
*/
public String getTopicConnectionFactoryName() {
return connectionFactoryName;
}
/**
* PUBLIC:
* Return the URL of the machine on the network that hosts the JMS Topic. This is a required property and must be configured.
*/
public String getTopicHostUrl() {
return (String)getRemoteContextProperties().get(Context.PROVIDER_URL);
}
/**
* INTERNAL:
* Initialize default properties.
*/
@Override
public void initialize() {
super.initialize();
topicName = DEFAULT_TOPIC;
connectionFactoryName = DEFAULT_CONNECTION_FACTORY;
}
/**
* INTERNAL:
* No-op, as the local connection does not need to be removed from JMSPublishingTransportManager.
* An application must close the connection directly if it is using the local connection as a listener.
*/
@Override
public void removeLocalConnection() {}
/**
* ADVANCED:
* This function is not supported for naming service other than JNDI or TransportManager.JNDI_NAMING_SERVICE.
*/
@Override
public void setNamingServiceType(int serviceType) {
if (serviceType != TransportManager.JNDI_NAMING_SERVICE) {
throw ValidationException.operationNotSupported("setNamingServiceType");
}
}
/**
* PUBLIC:
* flag used to toggle between j2EE/JMS spec compliant behavior and legacy behavior. Default value false causes external
* JMSTopicRemoteConnection to cache the TopicConnectionFactory and obtain TopicConnections, TopicSession and TopicPublishers
* every time executeCommand is called, and then closing them immediately. This is JMS and J2EE compliant, as the TopicConnection
* is never reused in different threads.
* True causes TopicConnection, TopicSession and TopicPublisher to be cached within the JMSTopicRemoteConnection and used for
* every executeCommand call. These objects can potentially used concurrently, which the JMS spec does not force
* providers to support.
*
*/
public void setShouldReuseJMSTopicPublisher(boolean reuseJMSTopicPublisher){
this.reuseJMSTopicPublisher = reuseJMSTopicPublisher;
}
/**
* PUBLIC:
* Configure the JMS Topic Connection Factory Name for the JMS Topic connections.
*/
public void setTopicConnectionFactoryName(String newTopicConnectionFactoryName) {
connectionFactoryName = newTopicConnectionFactoryName;
}
/**
* PUBLIC:
* Configure the URL of the machine on the network that hosts the JMS Topic. This is a required property and must be configured.
*/
public void setTopicHostUrl(String jmsHostUrl) {
getRemoteContextProperties().put(Context.PROVIDER_URL, jmsHostUrl);
rcm.getServiceId().setURL(jmsHostUrl);
}
}