/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.google.gwt.eclipse.oophm.launch;
import com.google.gwt.dev.shell.remoteui.MessageTransport;
import com.google.gwt.dev.shell.remoteui.MessageTransport.TerminationCallback;
import com.google.gwt.eclipse.oophm.devmode.ViewerServiceServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Listens for and accepts a single connection from a remote server wanting to
* dispatch UI events and receive commands.
*/
public class RemoteUIServer {
private static RemoteUIServer INSTANCE;
public synchronized static RemoteUIServer getInstance() throws IOException {
if (INSTANCE == null) {
INSTANCE = new RemoteUIServer();
INSTANCE.start();
}
return INSTANCE;
}
private final ServerSocket serverSocket;
private Thread connectionAcceptThread = null;
private Object privateInstanceLock = new Object();
private AtomicBoolean isStopped = new AtomicBoolean(false);
/**
* Create a new instance for the given launch configuration object. The
* communicator will listen for a connection on <code>requestedPort</code>. If
* <code>requestedPort</code> is set to <code>0</code>, then a port will
* automatically chosen. The automatically-chosen port can be queried via
* {@link #getPort()}.
*
* @throws IOException if there was a problem while attempting to set up the
* server socket
*/
private RemoteUIServer() throws IOException {
this.serverSocket = new ServerSocket(0);
this.serverSocket.setSoTimeout(2000);
}
/**
* Get the port on which the communicator is listening for a connection.
*/
public int getPort() {
return serverSocket.getLocalPort();
}
/**
* Stops the communicator. All open connections to the remote server will be
* terminated. Once this method has been called, the communicator cannot be
* restarted.
*/
public void stop() {
synchronized (privateInstanceLock) {
if (connectionAcceptThread == null) {
return;
}
}
isStopped.set(true);
try {
connectionAcceptThread.interrupt();
connectionAcceptThread.join();
} catch (InterruptedException e) {
// Ignore
}
try {
serverSocket.close();
} catch (IOException e) {
// Ignore this exception
}
}
/**
* Start the communicator. Invoking this method causes the communicator to
* start listening on the port given by {@link #getPort()} for a connection
* from the remote server. Once a connection has been made, the listening port
* will be closed (i.e. only one connection is allowed).
*
* @throws IllegalStateException if the {@link RemoteUIServer} has already
* been started, or if {@link #stop()} has been called
*/
private void start() {
// Check to see if we've already stopped the communicator
if (isStopped.get()) {
throw new IllegalStateException(getClass().getName() + " on port "
+ serverSocket.getLocalPort() + " has already been stopped.");
}
synchronized (privateInstanceLock) {
// Check to see if the communicator has already been started
if (connectionAcceptThread != null) {
throw new IllegalStateException(getClass().getName() + " on port "
+ serverSocket.getLocalPort() + " has already been started.");
}
// Create the connection acceptance thread
connectionAcceptThread = new Thread() {
@Override
public void run() {
/*
* Wait 2 seconds for a connection request. If no request comes in by
* that time, throw a SocketTimeoutException, and check and see if the
* communicator has been terminated. If not, repeat the process.
*/
while (!isStopped.get()) {
try {
final Socket acceptSocket = serverSocket.accept();
if (acceptSocket != null) {
// Do something here...
ViewerServiceServer viewerServiceServer = new ViewerServiceServer();
MessageTransport transport = new MessageTransport(
acceptSocket.getInputStream(),
acceptSocket.getOutputStream(), viewerServiceServer,
new TerminationCallback() {
public void onTermination(Exception e) {
try {
acceptSocket.close();
} catch (IOException ioe) {
// Ignore
}
}
});
viewerServiceServer.setTransport(transport);
transport.start();
}
} catch (SocketTimeoutException e) {
// Ignore and try again
} catch (IOException e) {
// Terminate the thread
isStopped.set(true);
return;
}
}
}
};
} // end synchronized(privateInstanceLock)
// Start the connection acceptance thread
connectionAcceptThread.start();
}
}