001: /*
002: * Copyright (C) The MX4J Contributors.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the MX4J License version 1.0.
006: * See the terms of the MX4J License in the documentation provided with this software.
007: */
008:
009: package javax.management.remote.rmi;
010:
011: import java.io.IOException;
012: import java.lang.ref.WeakReference;
013: import java.rmi.Remote;
014: import java.security.AccessControlContext;
015: import java.security.AccessController;
016: import java.security.PrivilegedAction;
017: import java.util.HashMap;
018: import java.util.Iterator;
019: import java.util.Map;
020: import javax.management.MBeanServer;
021: import javax.management.remote.JMXAuthenticator;
022: import javax.management.remote.JMXConnectorServer;
023: import javax.security.auth.Subject;
024:
025: import mx4j.log.Log;
026: import mx4j.log.Logger;
027: import mx4j.remote.MX4JRemoteUtils;
028:
029: /**
030: * @version $Revision: 1.14 $
031: */
032: public abstract class RMIServerImpl implements RMIServer {
033: private ClassLoader defaultClassLoader;
034: private MBeanServer server;
035: private Map environment;
036: private RMIConnectorServer connector;
037: private Map connections = new HashMap();
038: private final AccessControlContext context;
039:
040: public RMIServerImpl(Map environment) {
041: this .environment = new HashMap(environment);
042: this .context = AccessController.getContext();
043: }
044:
045: AccessControlContext getContext() {
046: return context;
047: }
048:
049: public abstract Remote toStub() throws IOException;
050:
051: protected abstract void export() throws IOException;
052:
053: protected abstract String getProtocol();
054:
055: protected abstract RMIConnection makeClient(String connectionId,
056: Subject subject) throws IOException;
057:
058: protected abstract void closeClient(RMIConnection client)
059: throws IOException;
060:
061: protected abstract void closeServer() throws IOException;
062:
063: public ClassLoader getDefaultClassLoader() {
064: return defaultClassLoader;
065: }
066:
067: public void setDefaultClassLoader(ClassLoader defaultClassLoader) {
068: this .defaultClassLoader = defaultClassLoader;
069: }
070:
071: public synchronized void setMBeanServer(MBeanServer server) {
072: this .server = server;
073: }
074:
075: public synchronized MBeanServer getMBeanServer() {
076: return server;
077: }
078:
079: public String getVersion() {
080: return "1.0 MX4J";
081: }
082:
083: public synchronized RMIConnection newClient(Object credentials)
084: throws IOException, SecurityException {
085: final Subject subject = authenticate(getEnvironment(),
086: credentials);
087:
088: final String connectionId = MX4JRemoteUtils.createConnectionID(
089: getProtocol(), null, -1, subject);
090:
091: try {
092: RMIConnection client = makeClient(connectionId, subject);
093:
094: WeakReference weak = new WeakReference(client);
095:
096: synchronized (connections) {
097: connections.put(connectionId, weak);
098: }
099:
100: connector.connectionOpened(connectionId,
101: "Opened connection " + client, null);
102:
103: return client;
104: } catch (IOException x) {
105: throw x;
106: } catch (RuntimeException x) {
107: throw x;
108: } catch (Exception x) {
109: throw new IOException(x.toString());
110: }
111: }
112:
113: private Subject authenticate(Map env, final Object credentials)
114: throws SecurityException {
115: Logger logger = getLogger();
116:
117: Subject subject = null;
118: final JMXAuthenticator authenticator = (JMXAuthenticator) env
119: .get(JMXConnectorServer.AUTHENTICATOR);
120: if (authenticator != null) {
121: if (logger.isEnabledFor(Logger.DEBUG))
122: logger
123: .debug("Authenticating new client using JMXAuthenticator "
124: + authenticator);
125: try {
126: // We must check that the code that provided the JMXAuthenticator
127: // has the permission to create a Subject
128: subject = (Subject) AccessController.doPrivileged(
129: new PrivilegedAction() {
130: public Object run() {
131: return authenticator
132: .authenticate(credentials);
133: }
134: }, getContext());
135: if (subject == null)
136: throw new SecurityException(
137: "JMXAuthenticator returned null Subject");
138: if (logger.isEnabledFor(Logger.TRACE))
139: logger.trace("Authentication successful");
140: } catch (SecurityException x) {
141: if (logger.isEnabledFor(Logger.TRACE))
142: logger.trace("Authentication failed", x);
143: throw x;
144: } catch (Throwable x) {
145: if (logger.isEnabledFor(Logger.TRACE))
146: logger.trace("Authentication failed", x);
147: throw new SecurityException(x.toString());
148: }
149: }
150: return subject;
151: }
152:
153: protected void clientClosed(RMIConnection client)
154: throws IOException {
155: // Here we arrive when
156: // 1. The server is closed
157: // 2. The client is closed
158: // We must ensure the connection is in both cases removed from the list of active connections
159:
160: String connectionID = client.getConnectionId();
161: WeakReference weak = null;
162: synchronized (connections) {
163: weak = (WeakReference) connections.remove(connectionID);
164: }
165: // TODO: maybe I am overzealous here, I could return silently
166: if (weak == null)
167: throw new IOException(
168: "Could not find active connection with ID "
169: + connectionID);
170:
171: RMIConnection connection = (RMIConnection) weak.get();
172: if (connection != client)
173: throw new IOException("Could not find active connection "
174: + client);
175:
176: closeClient(client);
177: connector.connectionClosed(client.getConnectionId(),
178: "Closed connection " + client, null);
179: }
180:
181: public synchronized void close() throws IOException {
182: // The process of closing the server does:
183: // 1. closeServer() --> unexports the server
184: // 2. for each client in connections:
185: // connection.close()
186: // clientClosed(client)
187: // closeClient(client) --> unexports the client
188:
189: // The process of closing the client does:
190: // 1. connection.close()
191: // clientClosed(client)
192: // closeClient(client) --> unexports the client
193:
194: IOException serverException = null;
195: try {
196: closeServer();
197: } catch (IOException x) {
198: serverException = x;
199: }
200:
201: try {
202: closeConnections();
203: } catch (IOException x) {
204: if (serverException != null)
205: throw serverException;
206: throw x;
207: }
208: }
209:
210: private void closeConnections() throws IOException {
211: IOException clientException = null;
212: synchronized (connections) {
213: while (!connections.isEmpty()) {
214: // Yes, create an iterator every time.
215: // While expensive, this is needed because connection.close() must
216: // be able to modify the connections Map, and we don't want
217: // to get ConcurrentModificationExceptions
218: Iterator entries = connections.entrySet().iterator();
219: Map.Entry entry = (Map.Entry) entries.next();
220: WeakReference weak = (WeakReference) entry.getValue();
221: RMIConnection connection = (RMIConnection) weak.get();
222: if (connection == null) {
223: // We can use the iterator to remove the entry,
224: // since we don't call close(), that modifies the collection
225: entries.remove();
226: continue;
227: } else {
228: try {
229: connection.close();
230: } catch (IOException x) {
231: if (clientException == null)
232: clientException = x;
233: }
234: }
235: }
236: }
237: if (clientException != null)
238: throw clientException;
239: }
240:
241: private Logger getLogger() {
242: return Log.getLogger(getClass().getName());
243: }
244:
245: Map getEnvironment() {
246: return environment;
247: }
248:
249: void setRMIConnectorServer(RMIConnectorServer cntorServer) {
250: this.connector = cntorServer;
251: }
252: }
|