| /* |
| * 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); |
| } |
| } |