| /* |
| * Copyright (c) 1998, 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: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 17/10/2008-1.1 Michael O'Brien |
| // - 251005: The default JNDI InitialContextFactory is modified from |
| // OC4J: oracle.j2ee.rmi.RMIInitialContextFactory to |
| // WebLogic: weblogic.jndi.WLInitialContextFactory |
| package org.eclipse.persistence.sessions.coordination; |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Map; |
| |
| import javax.naming.Context; |
| import javax.naming.NamingException; |
| |
| import org.eclipse.persistence.exceptions.CommunicationException; |
| import org.eclipse.persistence.exceptions.RemoteCommandManagerException; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.security.SecurableObjectHolder; |
| import org.eclipse.persistence.internal.sessions.coordination.ConnectToHostCommand; |
| import org.eclipse.persistence.internal.sessions.coordination.RemoteConnection; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Provide an abstract class that offers a common API to handling |
| * remote command connections. |
| * </p><p> |
| * <b>Description</b>: This class manages the remote connections to other RCM service |
| * instances and posts the local connection to this service instance in a name |
| * service so that other RCM service instances can connect to it. |
| * </p> |
| * @author Steven Vo |
| * @since OracleAS TopLink 10<i>g</i> (9.0.4) |
| */ |
| public abstract class TransportManager { |
| |
| /** The remote command connection to this transport */ |
| protected RemoteConnection localConnection; |
| |
| /** The RCM that manages this transport */ |
| protected RemoteCommandManager rcm; |
| |
| /** The type of naming service used to look up other connections */ |
| protected int namingServiceType; |
| |
| /** Properties to obtain the context used for local JNDI access */ |
| protected Hashtable localContextProperties; |
| |
| /** Properties to obtain the context used for remote JNDI access */ |
| protected Hashtable remoteContextProperties; |
| |
| /** Determines whether a connection should be discarded if an error occurs on it */ |
| protected boolean shouldRemoveConnectionOnError; |
| |
| /** Connections to other services */ |
| protected Hashtable<String, RemoteConnection> connectionsToExternalServices; |
| |
| /** Security util that is used to decrypt and encrypt password */ |
| protected SecurableObjectHolder securableObjectHolder; |
| |
| public static final boolean DEFAULT_REMOVE_CONNECTION_ON_ERROR_MODE = true; |
| |
| /** Valid values for naming service type */ |
| public static final int JNDI_NAMING_SERVICE = 0; |
| public static final int REGISTRY_NAMING_SERVICE = 1; |
| |
| /** Defaults for RMI applications */ |
| public static final String DEFAULT_URL_PROTOCOL = "rmi"; |
| public static final String DEFAULT_IIOP_URL_PROTOCOL = "corbaname"; |
| public static final String DEFAULT_URL_PORT = "23791"; |
| public static final String DEFAULT_IIOP_URL_PORT = "5555#"; |
| public static final int DEFAULT_NAMING_SERVICE = JNDI_NAMING_SERVICE; |
| |
| /** Default JNDI properties for remote access */ |
| public static final String DEFAULT_CONTEXT_FACTORY = "weblogic.jndi.WLInitialContextFactory"; |
| public static final String DEFAULT_DEDICATED_CONNECTION_KEY = "dedicated.connection"; |
| public static final String DEFAULT_DEDICATED_CONNECTION_VALUE = "true"; |
| public static final String DEFAULT_USER_NAME = "admin"; |
| |
| /** |
| * INTERNAL: |
| * Return a remote connection to the specified service |
| */ |
| public abstract RemoteConnection createConnection(ServiceId serviceId); |
| |
| /** |
| * INTERNAL: |
| * Does nothing by default. In case TransportManager doesn't use DiscoveryManager |
| * (createDiscoveryManager method retuns null) |
| * this method called during RCM initialization to create all the necessary connections. |
| * TransportManager ancestors that override createDiscoveryManager method to return null |
| * must override this method, too. |
| */ |
| public void createConnections() { |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is called by the remote command manager when this service should |
| * connect back ('handshake') to the service from which this remote connection came. |
| */ |
| public void connectBackToRemote(RemoteConnection connection) throws Exception { |
| ConnectToHostCommand command = new ConnectToHostCommand(); |
| command.setServiceId(rcm.getServiceId()); |
| connection.executeCommand(command); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a remote connection to this service |
| */ |
| public RemoteConnection getConnectionToLocalHost() { |
| return localConnection; |
| } |
| |
| /** |
| * INTERNAL: |
| * Put the remote connection to local host in naming service and return the of the created remote connection |
| */ |
| public abstract void createLocalConnection(); |
| |
| /** |
| * PUBLIC: |
| * Return the type of naming service used to look up remote connections to other |
| * service instances. |
| * |
| * @return The type of naming service used. |
| */ |
| public int getNamingServiceType() { |
| return namingServiceType; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the type of naming service used to look up remote connections to other |
| * service instances. The service type must be one of JNDI_NAMING_SERVICE or |
| * REGISTRY_NAMING_SERVICE. |
| */ |
| public void setNamingServiceType(int serviceType) { |
| namingServiceType = serviceType; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the user name used as the value to the SECURITY_PRINCIPAL key in the |
| * cached context properties. |
| */ |
| public String getUserName() { |
| return (String)getRemoteContextProperties().get(Context.SECURITY_PRINCIPAL); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the user name used as the value to the SECURITY_PRINCIPAL key in the |
| * cached context properties. |
| */ |
| public void setUserName(String userName) { |
| getRemoteContextProperties().put(Context.SECURITY_PRINCIPAL, userName); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the password used as the value to the SECURITY_CREDENTIALS key in |
| * the cached context properties. |
| */ |
| public String getPassword() { |
| return decrypt(getEncryptedPassword()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the encrypted (assumed) password used as the value to the |
| * SECURITY_CREDENTIALS key in the cached context properties. |
| */ |
| public String getEncryptedPassword() { |
| return (String) getRemoteContextProperties().get(Context.SECURITY_CREDENTIALS); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the password used as the value to the SECURITY_CREDENTIALS key in the |
| * cached context properties. |
| */ |
| public void setPassword(String password) { |
| if (password != null) { |
| getRemoteContextProperties().put(Context.SECURITY_CREDENTIALS, encrypt(password)); |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the encrypted password used as the value to the SECURITY_CREDENTIALS key in the |
| * cached context properties. |
| */ |
| public void setEncryptedPassword(String encryptedPassword) { |
| getRemoteContextProperties().put(Context.SECURITY_CREDENTIALS, encryptedPassword); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the context factory name used as the value to the INITIAL_CONTEXT_FACTORY |
| * key in the cached context properties. |
| */ |
| public String getInitialContextFactoryName() { |
| return (String)getRemoteContextProperties().get(Context.INITIAL_CONTEXT_FACTORY); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the context factory name used as the value to the INITIAL_CONTEXT_FACTORY |
| * key in the cached context properties. |
| */ |
| public void setInitialContextFactoryName(String contextFactoryName) { |
| getRemoteContextProperties().put(Context.INITIAL_CONTEXT_FACTORY, contextFactoryName); |
| } |
| |
| /** |
| * INTERNAL: |
| * Helper method to get a naming context. |
| */ |
| public Context getContext(Hashtable contextProperties) { |
| try { |
| return new javax.naming.InitialContext(contextProperties); |
| } catch (NamingException exception) { |
| RemoteCommandManagerException rcmException = RemoteCommandManagerException.errorObtainingContext(exception); |
| rcm.handleException(RemoteCommandManagerException.errorObtainingContext(exception)); |
| throw rcmException; |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the cached properties that will be used to create the initial context |
| * when doing remote JNDI lookups. |
| */ |
| public Hashtable getRemoteContextProperties() { |
| return remoteContextProperties; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the cached properties that will be used to create the initial context |
| * when doing remote JNDI lookups. |
| */ |
| public void setRemoteContextProperties(Hashtable properties) { |
| remoteContextProperties = properties; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the properties that will be used to create the initial context |
| * for local JNDI access. |
| */ |
| public Hashtable getLocalContextProperties() { |
| if (localContextProperties == null) { |
| localContextProperties = new Hashtable(); |
| } |
| return localContextProperties; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the properties that will be used to create the initial context |
| * for local JNDI access. |
| */ |
| public void setLocalContextProperties(Hashtable properties) { |
| localContextProperties = properties; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public RemoteCommandManager getRemoteCommandManager() { |
| return rcm; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public void setRemoteCommandManager(RemoteCommandManager rcm) { |
| this.rcm = rcm; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a remote Connection to a remote service. |
| */ |
| public void addConnectionToExternalService(RemoteConnection connection) { |
| if (connection == null) { |
| return; |
| } |
| try { |
| connectBackToRemote(connection); |
| connectionsToExternalServices.put(connection.getServiceId().getId(), connection); |
| Object[] args = { connection.getServiceId() }; |
| rcm.logDebug("received_connection_from", args); |
| } catch (Exception exception) { |
| try { |
| rcm.handleException(CommunicationException.errorSendingConnectionService(connection.toString(), exception)); |
| } catch (RuntimeException reThrownException) { |
| Object[] args = { connection.getServiceId(), reThrownException }; |
| rcm.logWarning("problem_adding_connection", args); |
| if (!shouldRemoveConnectionOnError) { |
| throw reThrownException; |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove a remote connection from the list of connections to receive remote commands. |
| */ |
| public void removeConnectionToExternalService(RemoteConnection connection) { |
| synchronized (this) { |
| connectionsToExternalServices.remove(connection.getServiceId().getId()); |
| connection.close(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove all remote connections from the list. |
| */ |
| public void removeAllConnectionsToExternalServices() { |
| synchronized (this) { |
| Enumeration connections = connectionsToExternalServices.elements(); |
| connectionsToExternalServices = new Hashtable(3); |
| |
| while (connections.hasMoreElements()) { |
| ((RemoteConnection)connections.nextElement()).close(); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public Map<String, RemoteConnection> getConnectionsToExternalServices() { |
| return connectionsToExternalServices; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns clone of the original map. |
| */ |
| public Map<String, RemoteConnection> getConnectionsToExternalServicesForCommandPropagation() { |
| return (Map<String, RemoteConnection>) connectionsToExternalServices.clone(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set whether connections to remote services should be disconnected when an |
| * error occurs. |
| */ |
| public void setShouldRemoveConnectionOnError(boolean shouldRemoveConnectionOnError) { |
| this.shouldRemoveConnectionOnError = shouldRemoveConnectionOnError; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return whether connections to remote services should be disconnected when an |
| * error occurs. |
| */ |
| public boolean shouldRemoveConnectionOnError() { |
| return shouldRemoveConnectionOnError; |
| } |
| |
| /** |
| * INTERNAL SECURITY: |
| * Set encryption class that will be loaded by the SecurableObjectHolder |
| */ |
| public void setEncryptionClassName(String encryptionClassName) { |
| SecurableObjectHolder oldHolder = securableObjectHolder; |
| |
| // re-initialize encryption mechanism |
| securableObjectHolder = new SecurableObjectHolder(); |
| securableObjectHolder.setEncryptionClassName(encryptionClassName); |
| |
| // re-encrypt password |
| if (hasPassword()) { |
| setPassword(oldHolder.getSecurableObject().decryptPassword(getEncryptedPassword())); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * @return true if a non null password has been set. |
| */ |
| protected boolean hasPassword() { |
| return getRemoteContextProperties().containsKey(Context.SECURITY_CREDENTIALS) && getRemoteContextProperties().get(Context.SECURITY_CREDENTIALS) != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize default properties. |
| */ |
| public void initialize() { |
| this.shouldRemoveConnectionOnError = DEFAULT_REMOVE_CONNECTION_ON_ERROR_MODE; |
| this.connectionsToExternalServices = new Hashtable(2); |
| |
| remoteContextProperties = new Hashtable(); |
| // Factory is not require inside the server, do not default. |
| //remoteContextProperties.put(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_CONTEXT_FACTORY); |
| remoteContextProperties.put(DEFAULT_DEDICATED_CONNECTION_KEY, DEFAULT_DEDICATED_CONNECTION_VALUE); |
| // User/password are not required inside the server, do not default. |
| //remoteContextProperties.put(Context.SECURITY_PRINCIPAL, DEFAULT_USER_NAME); |
| |
| this.securableObjectHolder = new SecurableObjectHolder(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Security method. |
| */ |
| protected String encrypt(String pwd) { |
| return securableObjectHolder.getSecurableObject().encryptPassword(pwd); |
| } |
| |
| /** |
| * INTERNAL: |
| * Security method called by the children classes |
| */ |
| protected String decrypt(String encryptedPwd) { |
| return securableObjectHolder.getSecurableObject().decryptPassword(encryptedPwd); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the context used for looking up in the JNDI space of the specified remote URL. |
| */ |
| public Context getRemoteHostContext(String remoteHostURL) { |
| Hashtable remoteProperties = (Hashtable)getRemoteContextProperties().clone(); |
| // Host is only required if JMS connection factory is not accessible from local JNDI. |
| if (remoteHostURL != null) { |
| remoteProperties.put(Context.PROVIDER_URL, remoteHostURL); |
| } |
| Object[] args = { remoteProperties }; |
| rcm.logDebug("context_props_for_remote_lookup", args); |
| |
| if (hasPassword()) { |
| // decrypt password just before looking up context. Calling |
| // getPassword() will decrypt it. |
| remoteProperties.put(Context.SECURITY_CREDENTIALS, getPassword()); |
| } |
| |
| return getContext(remoteProperties); |
| } |
| |
| /** |
| * ADVANCED: |
| * Factory of new DiscoveryManager for different transports. The RemoteCommandManger uses this method to create its DicscoveryManager. |
| * Sub-class of TransportManager should return special discovery if required. The default is discovery type is DiscoveryManager; |
| * If this method returns null then during initialization RemoteCommandManager |
| * calls createConnections method. |
| */ |
| public DiscoveryManager createDiscoveryManager() { |
| return new DiscoveryManager(rcm); |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove all remote connections for its list and the local connection from JNDI or JMS Subsriber |
| */ |
| public void discardConnections() { |
| removeLocalConnection(); |
| removeAllConnectionsToExternalServices(); |
| } |
| |
| /** |
| * ADVANCED: |
| * Remove the local connection from remote accesses. The implementation should set remove the local connection from JNDI or JMS and set it to null. |
| * This method is invoked internally by TopLink when the RCM is shutdown and should not be invoked by user's application. |
| */ |
| public abstract void removeLocalConnection(); |
| |
| /** |
| * Generic API to allow config to be set. |
| */ |
| public void setConfig(String config) { |
| } |
| |
| /** |
| * INTERNAL: |
| * Creates a new instance of {@code org.eclipse.persistence.sessions.coordination.rmi.RMITransportManager} class. |
| * @param rcm cache coordination manager |
| * @return new instance of RMI transport manager implementation |
| */ |
| public static TransportManager newSunCORBATransportManager(final RemoteCommandManager rcm) { |
| try { |
| return (TransportManager) PrivilegedAccessHelper.invokeConstructor( |
| PrivilegedAccessHelper.getConstructorFor( |
| Class.forName("org.eclipse.persistence.sessions.coordination.corba.sun.SunCORBATransportManager"), |
| new Class<?>[] { RemoteCommandManager.class }, false), |
| new Object[] { rcm }); |
| } catch (ReflectiveOperationException e) { |
| throw RemoteCommandManagerException.errorInitCorba("org.eclipse.persistence.sessions.coordination.corba.sun.SunCORBATransportManager", e); |
| } |
| } |
| |
| } |