blob: b1733f5ea1c93167c953d4d5bef608ee20f6d2e6 [file] [log] [blame]
/*
* 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";
/**
* Default constructor.
*/
protected TransportManager() {
}
/**
* 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<RemoteConnection> connections = connectionsToExternalServices.elements();
connectionsToExternalServices = new Hashtable(3);
while (connections.hasMoreElements()) {
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);
}
}
}