/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* $Header: /cvs/glassfish/appserv-api/src/java/com/sun/appserv/management/client/AppserverConnectionSource.java,v 1.2 2007/05/05 05:30:31 tcfujii Exp $
* $Revision: 1.2 $
* $Date: 2007/05/05 05:30:31 $
*/
package com.sun.appserv.management.client;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.io.IOException;
import java.io.File;
import java.net.MalformedURLException;
import java.security.KeyStore;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.HandshakeCompletedListener;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectionNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import com.sun.appserv.management.client.ConnectionSource;
import com.sun.appserv.management.util.jmx.JMXConnectorConnectionSource;
import com.sun.appserv.management.util.misc.MapUtil;
import com.sun.appserv.management.DomainRoot;
/**
Supports connectivity to Sun Appserver 8.1 and later (not available
prior to 8.1).
This is the only official way
to get a connection to the appserver.
<p>
Helper methods to simplify connecting are available in
{@link com.sun.appserv.management.helper.Connect}.
<p>
If the server is running with TLS enabled, then you must use the constructor
that includes TLSParams. Here is an example of how to connect using TLS:
<pre>
final File trustStore = new File( "~/.keystore" );
final char[] trustStorePassword = "changeme".toCharArray(); // or whatever it is
final HandshakeCompletedListener listener = new HandshakeCompletedListenerImpl();
final TrustStoreTrustManager trustMgr = new TrustStoreTrustManager( trustStore, trustStorePassword);
trustMgr.setPrompt( true );
final TLSParams tlsParams = new TLSParams( new X509TrustManager[] { trustMgr }, listener );
final AppserverConnectionSource src =
new AppserverConnectionSource( AppserverConnectionSource.PROTOCOL_RMI,
"localhost", 8686, "admin", "admin123",
tlsParams,
null );
final DomainRoot domainRoot = src.getDomainRoot();
</pre>
If security is not an issue, it is recommended to simply disable TLS on the
server. However, you can also connect using TLS whereby the
server certificate is blindly trusted:
<pre>
final TLSParams tlsParams = new TLSParams( TrustAnyTrustManager.getInstanceArray(), null );
final AppserverConnectionSource src =
new AppserverConnectionSource( AppserverConnectionSource.PROTOCOL_RMI,
"localhost", 8686, "admin", "admin123",
tlsParams,
null );
final DomainRoot domainRoot = src.getDomainRoot();
</pre>
@see com.sun.appserv.management.client.TrustStoreTrustManager
@see com.sun.appserv.management.client.TrustAnyTrustManager
@see com.sun.appserv.management.client.HandshakeCompletedListenerImpl
@see com.sun.appserv.management.client.TLSParams
*/
public final class AppserverConnectionSource
implements NotificationListener, ConnectionSource
{
private final String mHost;
private final int mPort;
private final String mProtocol;
private final String mUser;
private final String mPassword;
private final TLSParams mTLSParams;
private final Map<String,String> mReserved;
protected JMXConnector mJMXConnector;
/**
FIXME FIXME FIXME
This should be removed after the http connector supports the HandshakeCompletedListener
*/
private static final boolean DISABLE_HANDSHAKE_COMPLETED_CHECK = true;
private boolean
disableHandShakeCompletedCheck()
{
return( DISABLE_HANDSHAKE_COMPLETED_CHECK && mProtocol.equals( PROTOCOL_HTTP ) );
}
/**
@return true if the specified protocol is supported.
*/
public static boolean
isSupportedProtocol( final String protocol )
{
return( protocol != null &&
(
protocol.equals( PROTOCOL_HTTP ) ||
protocol.equals( PROTOCOL_RMI )
)
);
}
/**
[used internally]
*/
public final static String TRUST_MANAGERS_KEY = "TRUST_MANAGER_KEY";
/**
[used internally]
*/
public final static String HANDSHAKE_COMPLETED_LISTENER_KEY = "HandshakeCompletedListener";
private static final String PROTOCOL_PREFIX = "sun-as-";
/**
RMI protocol to the Appserver.
<b>Do not use the literal value of this constant in your code;
it is subject to change</b>
*/
public final static String PROTOCOL_RMI = PROTOCOL_PREFIX + "rmi";
/**
Default protocol to the Appserver eg PROTOCOL_RMI.
*/
public final static String DEFAULT_PROTOCOL = PROTOCOL_RMI;
/**
Internal unsupported protocol. Not supported for external use.
<b>Do not use the literal value of this constant in your code;
it is subject to change</b>
*/
public final static String PROTOCOL_HTTP = PROTOCOL_PREFIX + "http";
private static final String INTERNAL_HTTP = "s1ashttp";
private static final String INTERNAL_HTTPS = "s1ashttps";
/**
Packages for http(s) JMXConnectorProvider.
*/
private final static String HTTP_FACTORY_PACKAGES =
"com.sun.enterprise.admin.jmx.remote.protocol";
/**
Name of the default TrustStore file, located in the client's
home directory.
*/
public static final String DEFAULT_TRUST_STORE_NAME = ".asadmintruststore";
/**
The default pasword for {@link #DEFAULT_TRUST_STORE_NAME}.
*/
public static final String DEFAULT_TRUST_STORE_PASSWORD = "changeit";
/**
Create a new instance using the default protocol without TLS.
@param host hostname or IP address
@param port port to which to connect
@param user username
@param userPassword password for specified username
@param reserved reserved for future use
*/
public
AppserverConnectionSource(
String host,
int port,
String user,
String userPassword,
final Map<String,String> reserved)
{
this( DEFAULT_PROTOCOL, host, port, user, userPassword, reserved);
}
/**
Create a new instance connecting to the specified host/port with
the specified protocol without TLS.
<p>
<b>Note:</b>The only supported protocol is {@link #PROTOCOL_RMI}.
Use of any other protocol is not supported and these protocols are
subject to change or removal at any time.
@param protocol protocol to use eg PROTOCOL_RMI
@param host hostname or IP address
@param port port to which to connect
@param user username
@param userPassword password for specified username
@param reserved reserved for future use
*/
public
AppserverConnectionSource(
final String protocol,
final String host,
final int port,
final String user,
final String userPassword,
final Map<String,String> reserved )
{
this ( protocol, host, port, user, userPassword, null, reserved );
}
/**
Create a new instance which will use TLS for security if specified.
@param protocol protocol to use eg PROTOCOL_RMI
@param host hostname or IP address
@param port port to which to connect
@param user username
@param userPassword password for specified username
@param tlsParams (may be null if TLS is not desired)
@param reserved reserved for future use
@see com.sun.appserv.management.client.TLSParams
*/
public
AppserverConnectionSource(
final String protocol,
final String host,
final int port,
final String user,
final String userPassword,
final TLSParams tlsParams,
final Map<String,String> reserved )
{
if ( reserved != null && reserved.keySet().size() != 0 )
{
throw new IllegalArgumentException( "No parameters may be passed in 'reserved' Map" );
}
if ( isSupportedProtocol( protocol ) )
{
mHost = host;
mPort = port;
mProtocol = protocol;
mUser = user;
mPassword = userPassword;
mTLSParams = tlsParams;
mReserved = reserved;
}
else
{
throw new IllegalArgumentException( "unsupported protocol: " + protocol +
", use either PROTOCOL_RMI or PROTOCOL_HTTP" );
}
}
private Object
envGet( final String key )
{
return( mReserved == null ? null : mReserved.get( key ) );
}
private final boolean
useTLS()
{
return( mTLSParams != null );
}
private final X509TrustManager[]
getTrustManagers()
{
return( mTLSParams == null ? null : mTLSParams.getTrustManagers() );
}
private final HandshakeCompletedListener
getSuppliedHandshakeCompletedListener()
{
return( mTLSParams == null ? null : mTLSParams.getHandshakeCompletedListener() );
}
private Map<String,Object>
getCredentialsEnv(
final String user,
final String password )
{
final HashMap<String,Object> env = new HashMap<String,Object>();
final String[] credentials = new String[] { mUser, mPassword };
env.put( JMXConnector.CREDENTIALS, credentials );
return( env );
}
private static final String APPSERVER_JNDI_NAME = "/management/rmi-jmx-connector";
private void
warning( final String msg )
{
System.out.println( "\n***\nWARNING: " + msg );
}
private JMXConnector
createNew()
throws MalformedURLException, IOException
{
final Map<String,Object> env = getCredentialsEnv( mUser, mPassword );
// NetBeans fix; classloader must be more than system classloader
env.put("jmx.remote.protocol.provider.class.loader",
this.getClass().getClassLoader());
final HandshakeCompletedListenerImpl hcListener =
new HandshakeCompletedListenerImpl( getSuppliedHandshakeCompletedListener() );
JMXServiceURL url = null;
if ( mProtocol.equals( PROTOCOL_HTTP ) )
{
if ( useTLS() )
{
//env.put( TRUST_MANAGERS_KEY, getTrustManagers() );
final X509TrustManager[] tms = getTrustManagers();
if (tms != null && tms.length >= 1) {
env.put( TRUST_MANAGERS_KEY, tms[0]);
}
env.put( HANDSHAKE_COMPLETED_LISTENER_KEY, hcListener );
}
env.put( "com.sun.enterprise.as.http.auth", "BASIC" );
env.put( "USER", mUser );
env.put( "PASSWORD", mPassword );
// so our JMXConnectorProvider may be found
env.put( JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, HTTP_FACTORY_PACKAGES );
final String internalProtocol = useTLS() ? INTERNAL_HTTPS : INTERNAL_HTTP;
url = new JMXServiceURL( internalProtocol, mHost, mPort);
}
else if ( mProtocol.equals( PROTOCOL_RMI ) )
{
if ( useTLS() )
{
// the only way we can communicate with/control the RMI stub is through
// this special singleton class
final AdminRMISSLClientSocketFactoryEnvImpl rmiEnv =
AdminRMISSLClientSocketFactoryEnvImpl.getInstance();
rmiEnv.setHandshakeCompletedListener( hcListener );
rmiEnv.setTrustManagers( getTrustManagers() );
}
final String s = "service:jmx:rmi:///jndi/rmi://" +
mHost + ":" + mPort + APPSERVER_JNDI_NAME;
url = new JMXServiceURL( s );
}
else
{
assert( false );
}
final JMXConnector conn = JMXConnectorFactory.connect( url, env );
/*
If the connection was established with RMI, it could have been an insecure
connection if a on-TLS stub was downloaded. Verify that the
a TLS Handshake was actually completed.
*/
if ( ! disableHandShakeCompletedCheck() )
{
if ( useTLS() && hcListener.getLastEvent() == null )
{
conn.close();
throw new IOException( "Connection could not be established using TLS; server is not using TLS" );
}
}
else
{
//warning( "HandshakeCompletedCheck is temporarily disabled for PROTOCOL_HTTP" );
/* This has been commented out -- See CR: 6172198. HTTPS/JMX Connector Implementation
does not have to accept HandshakeCompletedListener for 8.1*/
}
conn.addConnectionNotificationListener( this, null, conn );
return( conn );
}
/**
Used internally as callback for {@link javax.management.NotificationListener}.
<b>DO NOT CALL THIS METHOD</b>.
*/
public void
handleNotification(
final Notification notifIn,
final Object handback)
{
if ( notifIn instanceof JMXConnectionNotification )
{
final JMXConnectionNotification notif = (JMXConnectionNotification)notifIn;
final String type = notif.getType();
if ( type.equals( JMXConnectionNotification.FAILED) ||
type.equals( JMXConnectionNotification.CLOSED ) )
{
mJMXConnector = null;
}
}
}
/**
If the connection has already been created, return the existing JMXConnector
unless 'forceNew' is true or the connection is currently null.
@param forceNew if a new connection should be created
@return JMXConnector
*/
public JMXConnector
getJMXConnector( final boolean forceNew )
throws IOException
{
if ( forceNew || mJMXConnector == null )
{
mJMXConnector = createNew();
getMBeanServerConnection( false ); // make sure it works...
}
return( mJMXConnector );
}
public MBeanServerConnection
getExistingMBeanServerConnection( )
{
try
{
return( getJMXConnector( false ).getMBeanServerConnection() );
}
catch( IOException e )
{
}
return( null );
}
/**
@return getJMXConnector( forceNew ).getMBeanServerConnection()
*/
public MBeanServerConnection
getMBeanServerConnection( final boolean forceNew )
throws IOException
{
return( getJMXConnector( forceNew ).getMBeanServerConnection() );
}
/**
{@link DomainRoot} will be returned. Upon return, AMX is
ready for use. If the server has just been started, there
may be a slight delay until AMX is ready for use; this method
returns only once AMX is ready for use.
@return a DomainRoot
*/
public DomainRoot
getDomainRoot()
throws IOException
{
final DomainRoot domainRoot =
ProxyFactory.getInstance( this ).getDomainRoot( true );
return domainRoot;
}
public String
toString()
{
return(
"protocol=" + mProtocol +
", host=" + mHost +
", port=" + mPort +
", user=" + mUser +
", useTLS={" + useTLS() + "}" +
", mReserved=" + (mReserved == null ? "null" : MapUtil.toString( mReserved )) );
}
}