/*******************************************************************************
* Copyright (c) 2009 IBM Corporation and others.
* 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
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.debug.core.zend.communication;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.php.debug.daemon.communication.ICommunicationDaemon;
import org.eclipse.php.internal.debug.core.Logger;
import org.eclipse.php.internal.debug.core.PHPDebugPlugin;
import org.eclipse.php.internal.debug.core.PHPDebugUtil;
import org.eclipse.php.internal.debug.core.daemon.AbstractDebuggerCommunicationDaemon;
import org.eclipse.php.internal.debug.core.debugger.DebuggerSettingsManager;
import org.eclipse.php.internal.debug.core.debugger.IDebuggerSettings;
import org.eclipse.php.internal.debug.core.debugger.IDebuggerSettingsListener;
import org.eclipse.php.internal.debug.core.preferences.PHPDebugCorePreferenceNames;
import org.eclipse.php.internal.debug.core.zend.debugger.ZendDebuggerSettingsConstants;
/**
* The debugger communication receiver holds a ServerSocket that remains open
* for the entire Eclipse running session and accepts debug requests from remote
* or local debuggers. Any changes in the preferences listening port definition
* is reflected in this listener by re-initializing the ServerSocket to listen
* on the new port.
*
* @author Shalom Gibly
*/
public class DebuggerCommunicationDaemon implements ICommunicationDaemon {
public static final String ZEND_DEBUGGER_ID = "org.eclipse.php.debug.core.zendDebugger"; //$NON-NLS-1$
private static class CommunicationDaemon extends AbstractDebuggerCommunicationDaemon {
private int port;
/**
* Constructs a new DebuggerCommunicationDaemon
*/
public CommunicationDaemon(int port) {
this.port = port;
init();
}
/**
* Returns the server socket port used for the debug requests listening
* thread.
*
* @return The port specified in the preferences.
*/
public int getReceiverPort() {
return port;
}
/**
* Returns the Zend's debugger ID.
*
* @return The debugger ID that is using this daemon (e.g. Zend debugger
* ID).
* @since PDT 1.0
*/
public String getDebuggerID() {
return DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID;
}
public boolean isDebuggerDaemon() {
return true;
}
/**
* Initialize a ServerSocket or a SSLServerSocket to listen for debug
* requests on a specified port. The port and the SSL definitions are
* defined in the workspace preferences.
*
* @return True, if the reset did not yield any errors; False,
* otherwise.
*/
public boolean resetSocket() {
stopListen();
int port = getReceiverPort();
try {
synchronized (lock) {
if (useSSL) {
SSLServerSocket sslServerSocket = (SSLServerSocket) SSLServerSocketFactory.getDefault()
.createServerSocket(port);
sslServerSocket.setEnabledCipherSuites(sslServerSocket.getSupportedCipherSuites());
serverSocket = sslServerSocket;
} else {
serverSocket = new ServerSocket(port);
}
startListen();
return true;
}
} catch (BindException exc) {
handleMultipleBindingError();
} catch (IOException e) {
Logger.logException(e);
}
return false;
}
/**
* Starts a connection on the given Socket. This method can be
* overridden by extending classes to create a different debug
* connection.
*
* @param socket
*/
protected synchronized void startConnection(Socket socket) {
new DebugConnection(socket);
}
}
private List<AbstractDebuggerCommunicationDaemon> daemons = new ArrayList<AbstractDebuggerCommunicationDaemon>();
private IPreferenceChangeListener defaultPortListener = null;
private IDebuggerSettingsListener debuggerSettingsListener = null;
private IPreferenceChangeListener sslChangeListener;
private static boolean useSSL;
/*
* Listen to any changes in the SSL preference.
*/
private class SSLChangeListener implements IPreferenceChangeListener {
public void preferenceChange(PreferenceChangeEvent event) {
if (event.getKey().equals(PHPDebugCorePreferenceNames.ZEND_DEBUG_ENCRYPTED_SSL_DATA)) {
Object newValueObj = event.getNewValue();
if (newValueObj != null) {
if (newValueObj instanceof String)
setUseSSL(Boolean.valueOf((String) newValueObj));
else if (newValueObj instanceof Boolean)
setUseSSL((Boolean) newValueObj);
}
if (newValueObj == null)
setUseSSL(false);
}
}
}
// A port change listener
private class DefaultPortListener implements IPreferenceChangeListener {
@Override
public void preferenceChange(PreferenceChangeEvent event) {
if (event.getKey().equals(PHPDebugCorePreferenceNames.ZEND_DEBUG_PORT)) {
reset();
}
}
}
private class DebuggerSettingsListener implements IDebuggerSettingsListener {
@Override
public void settingsAdded(IDebuggerSettings settings) {
if (getDebuggerID().equals(settings.getDebuggerId()))
reset();
}
@Override
public void settingsRemoved(IDebuggerSettings settings) {
if (getDebuggerID().equals(settings.getDebuggerId()))
reset();
}
@Override
public void settingsChanged(PropertyChangeEvent[] events) {
for (PropertyChangeEvent event : events) {
IDebuggerSettings settings = (IDebuggerSettings) event.getSource();
if (getDebuggerID().equals(settings.getDebuggerId())
&& event.getProperty().equals(ZendDebuggerSettingsConstants.PROP_CLIENT_PORT)) {
reset();
}
}
}
}
/**
* Creates and returns communication daemon that listens on given port. This
* method is dedicated to be used if there is a need to create a temporary
* daemon e.g. for communication test purposes. Clients should remember to
* call #stopListen() on daemon after it is not longer used.
*
* @param port
* @return new communication daemon
*/
public static AbstractDebuggerCommunicationDaemon createDaemon(int port) {
return new CommunicationDaemon(port);
}
/**
* Returns if there is a use of SSL encryption for this connection.
*
* @return True, iff there is a SSL use; False, otherwise.
*/
public synchronized boolean isUsingSSL() {
return useSSL;
}
/**
* Set the use of SSL encryption for the connection.
*
* @param enable
*/
public synchronized void setUseSSL(boolean enable) {
if (enable == isUsingSSL()) {
return;
}
// useSSLConnectionChanged = true;
useSSL = enable;
resetSocket();
}
@Override
public void init() {
useSSL = InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID)
.getBoolean(PHPDebugCorePreferenceNames.ZEND_DEBUG_ENCRYPTED_SSL_DATA, false);
registerListeners();
reset();
}
@Override
public boolean isListening(int port) {
for (ICommunicationDaemon daemon : daemons)
if (daemon.isListening(port))
return true;
return false;
}
@Override
public void startListen() {
for (ICommunicationDaemon daemon : daemons)
daemon.startListen();
}
@Override
public void stopListen() {
unregisterListeners();
for (ICommunicationDaemon daemon : daemons)
daemon.stopListen();
}
@Override
public boolean resetSocket() {
boolean allReset = true;
for (ICommunicationDaemon daemon : daemons)
if (!daemon.resetSocket())
allReset = false;
return allReset;
}
@Override
public void handleMultipleBindingError() {
// Won't be called
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getDebuggerID() {
return ZEND_DEBUGGER_ID;
}
@Override
public boolean isDebuggerDaemon() {
return true;
}
@Override
public boolean isInitialized() {
for (ICommunicationDaemon daemon : daemons)
if (!daemon.isInitialized())
return false;
return true;
}
private void registerListeners() {
if (defaultPortListener == null) {
defaultPortListener = new DefaultPortListener();
InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID).addPreferenceChangeListener(defaultPortListener);
}
if (debuggerSettingsListener == null) {
debuggerSettingsListener = new DebuggerSettingsListener();
DebuggerSettingsManager.INSTANCE.addSettingsListener(debuggerSettingsListener);
}
if (sslChangeListener == null) {
sslChangeListener = new SSLChangeListener();
InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID).addPreferenceChangeListener(sslChangeListener);
}
}
private void unregisterListeners() {
if (defaultPortListener != null) {
InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID).removePreferenceChangeListener(defaultPortListener);
}
if (debuggerSettingsListener != null) {
DebuggerSettingsManager.INSTANCE.removeSettingsListener(debuggerSettingsListener);
}
if (sslChangeListener != null) {
InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID).removePreferenceChangeListener(sslChangeListener);
}
}
private synchronized void reset() {
Set<Integer> ports = PHPDebugUtil.getDebugPorts(getDebuggerID());
List<AbstractDebuggerCommunicationDaemon> daemonsToRemove = new ArrayList<AbstractDebuggerCommunicationDaemon>();
// Shutdown daemons that should not listen anymore
for (AbstractDebuggerCommunicationDaemon daemon : daemons) {
boolean isRedundant = true;
for (int port : ports) {
if (daemon.getReceiverPort() == port) {
isRedundant = false;
break;
}
}
if (isRedundant) {
daemon.stopListen();
daemonsToRemove.add(daemon);
}
}
daemons.removeAll(daemonsToRemove);
// Start new daemons if there should be any
for (int port : ports) {
boolean isRunning = false;
for (AbstractDebuggerCommunicationDaemon daemon : daemons) {
if (daemon.getReceiverPort() == port) {
isRunning = true;
break;
}
}
if (!isRunning) {
AbstractDebuggerCommunicationDaemon newDaemon = new CommunicationDaemon(port);
daemons.add(newDaemon);
}
}
}
}