/******************************************************************************* * Copyright (c) 2009, 2011 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.xdebug.communication; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.preferences.IEclipsePreferences; 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.debug.core.*; import org.eclipse.debug.core.model.IProcess; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.php.debug.daemon.communication.ICommunicationDaemon; import org.eclipse.php.internal.debug.core.IPHPDebugConstants; import org.eclipse.php.internal.debug.core.PHPDebugCoreMessages; 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.AbstractDebuggerConfiguration; 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.launching.PHPProcess; import org.eclipse.php.internal.debug.core.launching.XDebugLaunch; import org.eclipse.php.internal.debug.core.pathmapper.PathMapper; import org.eclipse.php.internal.debug.core.pathmapper.PathMapperRegistry; import org.eclipse.php.internal.debug.core.preferences.PHPDebuggersRegistry; import org.eclipse.php.internal.debug.core.preferences.PHPProjectPreferences; import org.eclipse.php.internal.debug.core.sourcelookup.PHPSourceLookupDirector; import org.eclipse.php.internal.debug.core.xdebug.IDELayerFactory; import org.eclipse.php.internal.debug.core.xdebug.XDebugPreferenceMgr; import org.eclipse.php.internal.debug.core.xdebug.XDebugPreferenceMgr.AcceptRemoteSession; import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpBreakpointFacade; import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpLogger; import org.eclipse.php.internal.debug.core.xdebug.dbgp.XDebugDebuggerSettingsConstants; import org.eclipse.php.internal.debug.core.xdebug.dbgp.model.DBGpMultiSessionTarget; import org.eclipse.php.internal.debug.core.xdebug.dbgp.model.DBGpTarget; import org.eclipse.php.internal.debug.core.xdebug.dbgp.session.DBGpSession; import org.eclipse.php.internal.debug.core.xdebug.dbgp.session.DBGpSessionHandler; import org.eclipse.php.internal.debug.core.xdebug.dbgp.session.IDBGpSessionListener; import org.eclipse.php.internal.server.core.Server; import org.eclipse.php.internal.server.core.manager.ServersManager; import org.eclipse.php.ui.util.LinkMessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.*; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.osgi.service.prefs.BackingStoreException; import com.ibm.icu.text.MessageFormat; /** * XDebug communication daemon. * * @author Shalom Gibly * @since PDT 1.0 */ @SuppressWarnings("restriction") public class XDebugCommunicationDaemon implements ICommunicationDaemon { public static final String XDEBUG_DEBUGGER_ID = "org.eclipse.php.debug.core.xdebugDebugger"; //$NON-NLS-1$ private class CommunicationDaemon extends AbstractDebuggerCommunicationDaemon { private class PromptUser implements Runnable { private DBGpSession session; private boolean result; public boolean isResult() { return result; } public PromptUser(DBGpSession session) { this.session = session; } public void run() { String insert = session.getRemoteAddress().getCanonicalHostName() + "/" //$NON-NLS-1$ + session.getRemoteAddress().getHostAddress(); String message = MessageFormat.format(PHPDebugCoreMessages.XDebugMessage_remoteSessionPrompt, new Object[] { insert }); result = MessageDialog.openQuestion(Display.getDefault().getActiveShell(), PHPDebugCoreMessages.XDebugMessage_remoteSessionTitle, message); } } private class RequestVerifier { private DBGpSession session; public void verify(DBGpSession session) { if (!XDebugPreferenceMgr.getWarnNoLocalhostSession()) { return; } this.session = session; String message = null; int messageType = 0; AcceptRemoteSession acceptSession = XDebugPreferenceMgr.getAcceptRemoteSession(); if ((acceptSession == AcceptRemoteSession.localhost || acceptSession == AcceptRemoteSession.off) && !this.session.getRemoteAddress().isLoopbackAddress()) { message = PHPDebugCoreMessages.XDebugCommunicationDaemon_Remote_debug_session_does_not_refer_to_localhost; messageType = MessageDialog.WARNING; } else if (acceptSession == AcceptRemoteSession.off && this.session.getRemoteAddress().isLoopbackAddress()) { messageType = MessageDialog.INFORMATION; message = PHPDebugCoreMessages.XDebugCommunicationDaemon_Remote_debug_session_for_localhost; } else { return; } openMessage(message, messageType); } private void openMessage(final String message, final int messageType) { PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { final Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); final MessageDialog dialog = new LinkMessageDialog(shell, PHPDebugCoreMessages.XDebugCommunicationDaemon_XDebug_remote_session_title, null, message, messageType, new String[] { IDialogConstants.OK_LABEL }, 0) { @Override protected void linkActivated() { AbstractDebuggerConfiguration configuration = PHPDebuggersRegistry .getDebuggerConfiguration(XDEBUG_DEBUGGER_ID); configuration.openConfigurationDialog(getShell()); } @Override protected Control createDialogArea(Composite composite) { Control control = super.createDialogArea(composite); final Button button = new Button(composite, SWT.CHECK); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1); button.setLayoutData(gridData); button.setText( PHPDebugCoreMessages.XDebugCommunicationDaemon_Dont_show_this_message_again); button.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { IEclipsePreferences preferences = InstanceScope.INSTANCE .getNode(PHPDebugPlugin.ID); preferences.putBoolean( XDebugPreferenceMgr.XDEBUG_PREF_WARN_NO_LOCALHOST_SESSION, !button.getSelection()); try { preferences.flush(); } catch (BackingStoreException e1) { } } }); return control; } }; if (shell != null) { shell.forceActive(); shell.setActive(); } dialog.open(); } }); } } private int port; public CommunicationDaemon(int port) { this.port = port; init(); } public int getReceiverPort() { return port; } public boolean isDebuggerDaemon() { return true; } public String getDebuggerID() { return XDebugCommunicationDaemon.XDEBUG_DEBUGGER_ID; } /** * Starts a connection handling thread on the given Socket. * * @param socket */ protected void startConnection(Socket socket) { /* * A socket has been accepted by the listener. This runs on the * listener thread so we should make damn sure we don't throw an * exception here otherwise it will abort that thread. */ if (DBGpLogger.debugSession()) { DBGpLogger.debug("Connection established: " + socket.toString()); //$NON-NLS-1$ } try { DBGpSession session = new DBGpSession(socket); if (session.isActive()) { if (!DBGpSessionHandler.getInstance().fireSessionAdded(session)) { // Session not taken, we want to create a launch (new RequestVerifier()).verify(session); AcceptRemoteSession aSess = XDebugPreferenceMgr.getAcceptRemoteSession(); if (aSess != AcceptRemoteSession.off) { if (aSess == AcceptRemoteSession.localhost && session.getRemoteAddress().isLoopbackAddress() == false) { session.endSession(); } else if (aSess == AcceptRemoteSession.prompt) { PromptUser prompt = new PromptUser(session); Display.getDefault().syncExec(prompt); if (prompt.isResult()) { createLaunch(session); } else { session.endSession(); } } else { /* * session was either local host or from any * outside one and preferences allow it. */ createLaunch(session); } } else { // reject the session session.endSession(); } } } } catch (Exception e) { DBGpLogger.logException("Unexpected Exception: Listener thread still listening", //$NON-NLS-1$ this, e); } } /** * create a launch and appropriate debug targets to automate launch * initiation. If any problems occurred, we can throw the session away * using session.endSession(); * * @param session * the DBGpSession. * @throws CoreException */ private void createLaunch(DBGpSession session) throws CoreException { boolean stopAtFirstLine = PHPProjectPreferences.getStopAtFirstLine(null); DBGpTarget target = null; PathMapper mapper = null; PHPSourceLookupDirector srcLocator = new PHPSourceLookupDirector(); srcLocator.setSourcePathComputer(DebugPlugin.getDefault().getLaunchManager() .getSourcePathComputer("org.eclipse.php.debug.core.sourcePathComputer.php")); //$NON-NLS-1$ ILaunchConfigurationType type = null; ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager(); if (session.getSessionId() == null) { // WEB launch type = lm.getLaunchConfigurationType(IPHPDebugConstants.PHPRemoteLaunchType); } else { // CLI launch type = lm.getLaunchConfigurationType(IPHPDebugConstants.PHPEXELaunchType); } ILaunchConfiguration launchConfig = type.newInstance(null, PHPDebugCoreMessages.XDebugMessage_remoteSessionTitle); srcLocator.initializeDefaults(launchConfig); srcLocator.initializeParticipants(); ILaunch remoteLaunch = new XDebugLaunch(launchConfig, ILaunchManager.DEBUG_MODE, srcLocator); // Add the remote launch to the launch manager DebugPlugin.getDefault().getLaunchManager().addLaunch(remoteLaunch); boolean multiSession = XDebugPreferenceMgr.useMultiSession(); if (session.getSessionId() == null && !multiSession) { // Non multi-session web launch target = new DBGpTarget(remoteLaunch, null, null, session.getIdeKey(), null, stopAtFirstLine); IProcess process = new PHPProcess(remoteLaunch, PHPDebugCoreMessages.XDebugWebLaunchConfigurationDelegate_PHP_process); process.setAttribute(IProcess.ATTR_PROCESS_TYPE, IPHPDebugConstants.PHPProcessType); ((DBGpTarget) target).setProcess(process); ((PHPProcess) process).setDebugTarget(target); remoteLaunch.addProcess(process); /* * try to locate a relevant server definition so we can get its * path mapper */ Server server = null; Server[] servers = ServersManager.getServers(); for (int i = 0; i < servers.length; i++) { if (servers[i].getPort() == session.getRemotePort() && servers[i].getHost().equalsIgnoreCase(session.getRemoteHostname())) { server = servers[i]; break; } } if (server != null) { mapper = PathMapperRegistry.getByServer(server); } if (mapper == null) { /* * Create a temporary path mapper, we may look to holding * these via the pathmapper registry in the future but they * would be persisted. */ mapper = new PathMapper(); } /* * Need to add ourselves as a session listener for future * sessions. */ DBGpSessionHandler.getInstance().addSessionListener((IDBGpSessionListener) target); } else { // CLI launch or multisession web launch: create a single target target = new DBGpTarget(remoteLaunch, null /* no script name */, session.getIdeKey(), session.getSessionId(), stopAtFirstLine); // Create a temporary path mapper mapper = new PathMapper(); } // Set up the target with the relevant connections target.setPathMapper(mapper); target.setSession(session); session.setDebugTarget(target); if (multiSession && session.getSessionId() == null) { // We are a multisession web launch DBGpMultiSessionTarget multiSessionTarget = new DBGpMultiSessionTarget(remoteLaunch, null, null, session.getIdeKey(), stopAtFirstLine); DBGpSessionHandler.getInstance().addSessionListener((IDBGpSessionListener) multiSessionTarget); remoteLaunch.addDebugTarget(multiSessionTarget); multiSessionTarget.sessionReceived((DBGpBreakpointFacade) IDELayerFactory.getIDELayer(), XDebugPreferenceMgr.createSessionPreferences(), target, mapper); } else { // Not a mult-isession web launch, so just add to the launch remoteLaunch.addDebugTarget(target); // Tell the target it now has a session. target.sessionReceived((DBGpBreakpointFacade) IDELayerFactory.getIDELayer(), XDebugPreferenceMgr.createSessionPreferences()); /* * Probably could do waitForInitialSession as session has * already been set. */ } } } private List<AbstractDebuggerCommunicationDaemon> daemons = new ArrayList<AbstractDebuggerCommunicationDaemon>(); private IPreferenceChangeListener defaultPortListener = null; private IDebuggerSettingsListener debuggerSettingsListener = null; // A port change listener private class DefaultPortListener implements IPreferenceChangeListener { @Override public void preferenceChange(PreferenceChangeEvent event) { if (event.getKey().equals(XDebugPreferenceMgr.XDEBUG_PREF_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(XDebugDebuggerSettingsConstants.PROP_CLIENT_PORT)) { reset(); } } } } @Override public void init() { 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 XDEBUG_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); } } private void unregisterListeners() { if (defaultPortListener != null) { InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID).addPreferenceChangeListener(defaultPortListener); } if (debuggerSettingsListener != null) { DebuggerSettingsManager.INSTANCE.addSettingsListener(debuggerSettingsListener); } } private synchronized void reset() { Set<Integer> ports = PHPDebugUtil.getDebugPorts(getDebuggerID()); List<AbstractDebuggerCommunicationDaemon> daemonsToSet = 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) { daemonsToSet.add(daemon); isRedundant = false; break; } } if (isRedundant) { daemon.stopListen(); } } // 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); daemonsToSet.add(newDaemon); } } daemons = daemonsToSet; } }