/******************************************************************************* * 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.devmode; import com.google.gwt.dev.shell.remoteui.MessageTransport; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.DevModeRequest; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest.AddLog.LogType; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest.Initialize; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest.RequestType; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Response; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Response.DevModeResponse; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Response.ViewerResponse; import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Response.ViewerResponse.CapabilityExchange.Capability; import com.google.gwt.dev.shell.remoteui.RequestProcessor; import com.google.gwt.eclipse.core.GWTPluginLog; import com.google.gwt.eclipse.oophm.Activator; import com.google.gwt.eclipse.oophm.LogSniffer; import com.google.gwt.eclipse.oophm.model.BrowserTab; import com.google.gwt.eclipse.oophm.model.BrowserTab.ModuleHandle; import com.google.gwt.eclipse.oophm.model.IModelNode; import com.google.gwt.eclipse.oophm.model.LaunchConfiguration; import com.google.gwt.eclipse.oophm.model.Log; import com.google.gwt.eclipse.oophm.model.LogEntry; import com.google.gwt.eclipse.oophm.model.LogEntry.Data; import com.google.gwt.eclipse.oophm.model.WebAppDebugModel; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IProcess; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * A server for the ViewerService. The Development Mode server implements a ViewerService client, * which makes requests to this server. */ public class ViewerServiceServer implements RequestProcessor { /** * This struct is used to store information about a logging handle. Each handle corresponds to a * particular log branch entry (which may be the root log entry in some cases), and a module name. * * The module name is necessary because multiple log handles can be mapped to the same physical * logger. This happens when multiple modules are loaded in the same browser tab. In this case, * the log entries that are created must specify the module for which the log message was * intended. This struct provides that information. */ private static class LogHandleInfo { private final ModuleHandle moduleHandle; private final LogEntry<IModelNode> logBranch; public LogHandleInfo(ModuleHandle moduleHandle, LogEntry<IModelNode> logBranch) { this.moduleHandle = moduleHandle; this.logBranch = logBranch; } } private static LogEntry.Data createLogEntryDataFromLogEntryMessage( ViewerRequest.LogData msgLogData) { String helpInfoText = null; String helpInfoURL = null; if (msgLogData.getHelpInfo() != null) { helpInfoText = msgLogData.getHelpInfo().getText(); helpInfoURL = msgLogData.getHelpInfo().getUrl(); } boolean needsAttention = msgLogData.hasNeedsAttention() ? msgLogData.getNeedsAttention() : false; LogEntry.Data logEntryData = new LogEntry.Data(msgLogData.getSummary(), msgLogData.getDetails(), msgLogData.getLevel(), helpInfoURL, helpInfoText, System.currentTimeMillis(), needsAttention); return logEntryData; } private final Object privateInstanceLock = new Object(); private final AtomicInteger nextLoggerHandleId = new AtomicInteger(1); private final ConcurrentHashMap<Integer, LogHandleInfo> loggingHandleMap = new ConcurrentHashMap<Integer, LogHandleInfo>(); private MessageTransport transport = null; private LaunchConfiguration launchConfiguration = null; @Override public Response execute(Request request) throws Exception { try { ViewerRequest viewerRequest = request.getViewerRequest(); RequestType requestType = viewerRequest.getRequestType(); if (getLaunchConfiguration() == null && requestType != null && requestType != RequestType.INITIALIZE) { throw new IllegalStateException("INITIALIZE request must be the first request"); } if (requestType != null) { switch (requestType) { case INITIALIZE: return processInitialize(request.getViewerRequest().getInitialize()); case CAPABILITY_EXCHANGE: return processCapabilityExchange(); case ADD_LOG: return processAddLog(request.getViewerRequest().getAddLog()); case ADD_LOG_BRANCH: return processAddLogBranch(request.getViewerRequest().getAddLogBranch()); case ADD_LOG_ENTRY: return processAddLogEntry(request.getViewerRequest().getAddLogEntry()); case DISCONNECT_LOG: return processDisconnectLog(request.getViewerRequest().getDisconnectLog() .getLogHandle()); default: { break; } } } throw new IllegalArgumentException( "Unknown ViewerService Request: The ViewerService cannot handle requests of type " + requestType == null ? "(unknown)" : requestType.name()); } catch (Exception e) { GWTPluginLog.logError(e, "Failed to execute request"); // Re-throw the exception so we respond with a failure throw e; } } /** * NOTE: This method will probably be removed in the future. See * http://code.google.com/p/google-plugin-for-eclipse/issues/detail?id=10 */ public void setTransport(MessageTransport transport) { synchronized (privateInstanceLock) { if (this.transport == null) { this.transport = transport; } } } private Response buildResponse(ViewerResponse.Builder viewerResponseBuilder) { Response.Builder responseBuilder = Response.newBuilder(); if (viewerResponseBuilder != null) { responseBuilder.setViewerResponse(viewerResponseBuilder); } return responseBuilder.build(); } private int createLoggingHandleAndAddToMap(ModuleHandle moduleHandle, Log<? extends IModelNode> newLog) { return createLoggingHandleAndAddToMap(moduleHandle, newLog.getRootLogEntry()); } @SuppressWarnings("unchecked") private int createLoggingHandleAndAddToMap(ModuleHandle moduleHandle, LogEntry<? extends IModelNode> newLog) { int handle = nextLoggerHandleId.getAndIncrement(); // TODO: Can we improve type safety here? LogHandleInfo logInfo = new LogHandleInfo(moduleHandle, (LogEntry<IModelNode>) newLog); loggingHandleMap.put(Integer.valueOf(handle), logInfo); return handle; } private LaunchConfiguration getLaunchConfiguration() { synchronized (privateInstanceLock) { return launchConfiguration; } } /** * NOTE: This method will probably be removed in the future. See * http://code.google.com/p/google-plugin-for-eclipse/issues/detail?id=10 */ private MessageTransport getTransport() { synchronized (privateInstanceLock) { return transport; } } /** * NOTE: This method is likely to move/change in the future. See * http://code.google.com/p/google-plugin-for-eclipse/issues/detail?id=10 */ private void performDevModeServiceCapabilityExchange(final DevModeServiceClient client) { Thread t = new Thread(new Runnable() { @Override public void run() { boolean supportsRestart = false; try { List<DevModeResponse.CapabilityExchange.Capability> supportedDevModeServerCapabilities = client.checkCapabilities(); supportsRestart = DevModeServiceClient.checkCapability(supportedDevModeServerCapabilities, DevModeRequest.RequestType.RESTART_WEB_SERVER); } catch (Exception e) { Activator .getDefault() .getLog() .log( new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unable to determine whether or not the web server supports restarts.", e)); } if (supportsRestart) { getLaunchConfiguration().setSupportsRestartWebServer(); } } }); t.setName("Dev Mode Capability Exchange"); t.setDaemon(true); t.start(); } private Response processAddLog(ViewerRequest.AddLog addLog) { LogType type = addLog.getType(); if (type != null) { switch (type) { case MODULE: { return processAddModuleLog(addLog.getModuleLog()); } default: { break; } } } throw new IllegalArgumentException( "Unknown Log Type: The ViewerService cannot add logs of type " + type == null ? "(unknown)" : type.name()); } private Response processAddLogBranch(ViewerRequest.AddLogBranch addLogBranch) { LogHandleInfo logHandleInfo = loggingHandleMap.get(Integer.valueOf(addLogBranch.getParentLogHandle())); if (logHandleInfo == null) { throw new IllegalArgumentException("Log has not been registered"); } LogEntry.Data logEntryData = createLogEntryDataFromLogEntryMessage(addLogBranch.getLogData()); LogEntry<IModelNode> newLog = new LogEntry<IModelNode>(logEntryData, addLogBranch.getIndexInParent(), logHandleInfo.moduleHandle); // Log this event Data parentLogData = logHandleInfo.logBranch.getLogData(); LogSniffer.log( "AddLogBranch<{0}>: idx({1,number,#}) lvl({2}) attn({3}) label({4}) plabel({5})", getLaunchConfiguration().getName(), addLogBranch.getIndexInParent(), logEntryData.getLogLevel(), logEntryData.getAttentionLevel(), logEntryData.getLabel(), (parentLogData != null) ? parentLogData.getLabel() : ""); logHandleInfo.logBranch.addChild(newLog); int handle = createLoggingHandleAndAddToMap(logHandleInfo.moduleHandle, newLog); ViewerResponse.AddLogBranch.Builder addLogBranchResponseBuilder = ViewerResponse.AddLogBranch.newBuilder(); addLogBranchResponseBuilder.setLogHandle(handle); ViewerResponse.Builder viewerResponseBuilder = ViewerResponse.newBuilder(); viewerResponseBuilder.setResponseType(ViewerResponse.ResponseType.ADD_LOG_BRANCH); viewerResponseBuilder.setAddLogBranch(addLogBranchResponseBuilder); return buildResponse(viewerResponseBuilder); } private Response processAddLogEntry(ViewerRequest.AddLogEntry addLogEntry) { LogHandleInfo logHandleInfo = loggingHandleMap.get(Integer.valueOf(addLogEntry.getLogHandle())); if (logHandleInfo == null) { throw new IllegalArgumentException("Log for handle " + addLogEntry.getLogHandle() + " has not been registered"); } LogEntry.Data logEntryData = createLogEntryDataFromLogEntryMessage(addLogEntry.getLogData()); LogEntry<IModelNode> newLogEntry = new LogEntry<IModelNode>(logEntryData, addLogEntry.getIndexInLog(), logHandleInfo.moduleHandle); // Log this event Data parentLogData = logHandleInfo.logBranch.getLogData(); LogSniffer.log("AddLogEntry<{0}>: idx({1,number,#}) lvl({2}) attn({3}) label({4}) plabel({5})", getLaunchConfiguration().getName(), addLogEntry.getIndexInLog(), logEntryData.getLogLevel(), logEntryData.getAttentionLevel(), logEntryData.getLabel(), (parentLogData != null) ? parentLogData.getLabel() : ""); logHandleInfo.logBranch.addChild(newLogEntry); // TODO: Return the proper response type (which we need to define); all // that really matters right now is that we ACK return buildResponse(null); } private Response processAddModuleLog(ViewerRequest.AddLog.ModuleLog moduleLog) { ModuleHandle moduleHandle = null; // See if we can find a browser tab which matches the given criteria BrowserTab tab = launchConfiguration.findBrowserTab(moduleLog.getUserAgent(), moduleLog.getUrl(), moduleLog.getTabKey(), moduleLog.getSessionKey()); if (tab == null) { // We couldn't find an existing browser tab; create a new one byte[] iconBytes = null; if (moduleLog.getIcon() != null) { iconBytes = moduleLog.getIcon().toByteArray(); } BrowserTab.Info newTabInfo = new BrowserTab.Info(moduleLog.getTabKey(), moduleLog.getUserAgent(), moduleLog.getUrl(), moduleLog.getRemoteHost(), moduleLog.getSessionKey(), iconBytes); tab = launchConfiguration.addBrowserTab(newTabInfo, moduleLog.getName()); moduleHandle = tab.getModules().get(0); } else { moduleHandle = tab.addModule(moduleLog.getName(), moduleLog.getSessionKey()); } /* * TODO: Consider moving this logging handle logic into the BrowserTab class. */ // Create a logging handle for the new module logger int logHandle = createLoggingHandleAndAddToMap(moduleHandle, tab.getLog()); ViewerResponse.AddLog.Builder addLogResponseBuilder = ViewerResponse.AddLog.newBuilder(); addLogResponseBuilder.setLogHandle(logHandle); ViewerResponse.Builder viewerResponseBuilder = ViewerResponse.newBuilder(); viewerResponseBuilder.setResponseType(ViewerResponse.ResponseType.ADD_LOG); viewerResponseBuilder.setAddLog(addLogResponseBuilder); return buildResponse(viewerResponseBuilder); } /** * TODO: Should we somehow be asking the Development Mode View what its capabilities are (instead * of assuming what they are)? If we do that, we'll have to change the server implementation so * that it only process messages for which the Dev Mode View has capabilities. * * The capabilities reported back by this method are a basic set of capabilities that must be * present in order to do anything at all. As a result, we don't need to query the view for its * capability set at this time. */ private Response processCapabilityExchange() { ViewerResponse.CapabilityExchange.Builder capabilityExchangeBuilder = ViewerResponse.CapabilityExchange.newBuilder(); Capability.Builder c1 = Capability.newBuilder(); c1.setCapability(RequestType.CAPABILITY_EXCHANGE); capabilityExchangeBuilder.addCapabilities(c1); Capability.Builder c2 = Capability.newBuilder(); c2.setCapability(RequestType.ADD_LOG); capabilityExchangeBuilder.addCapabilities(c2); Capability.Builder c3 = Capability.newBuilder(); c3.setCapability(RequestType.ADD_LOG_BRANCH); capabilityExchangeBuilder.addCapabilities(c3); Capability.Builder c4 = Capability.newBuilder(); c4.setCapability(RequestType.ADD_LOG_ENTRY); capabilityExchangeBuilder.addCapabilities(c4); Capability.Builder c5 = Capability.newBuilder(); c5.setCapability(RequestType.DISCONNECT_LOG); capabilityExchangeBuilder.addCapabilities(c5); ViewerResponse.Builder viewerResponseBuilder = ViewerResponse.newBuilder(); viewerResponseBuilder.setResponseType(ViewerResponse.ResponseType.CAPABILITY_EXCHANGE); viewerResponseBuilder.setCapabilityExchange(capabilityExchangeBuilder); return buildResponse(viewerResponseBuilder); } private Response processDisconnectLog(int logHandle) { /* * TODO: It is probably no longer the case that messages for a disconnected logger can come over * the wire. As such, it is probably safe to remove logging handles from the map on * disconnection. */ LogHandleInfo logHandleInfo = loggingHandleMap.get(Integer.valueOf(logHandle)); if (logHandleInfo == null) { throw new IllegalArgumentException("Log has not been registered"); } // Log this event Data logData = logHandleInfo.logBranch.getLogData(); LogSniffer.log("DisconnectLog: label({0})", (logData != null) ? logData.getLabel() : ""); IModelNode entity = logHandleInfo.logBranch.getLog().getEntity(); if (entity instanceof BrowserTab) { BrowserTab tab = (BrowserTab) entity; if (!tab.removeModule(logHandleInfo.moduleHandle)) { Activator .getDefault() .getLog() .log( new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Request to unload module " + logHandleInfo.moduleHandle.getName() + ". This module has not been loaded in browser tab " + tab.getName())); } } // TODO: Return the proper response type (which we need to define); all // that really matters right now is that we ACK return buildResponse(null); } private Response processInitialize(Initialize initialize) { String clientId = initialize.getClientId(); assert (clientId != null); ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); ILaunch[] launches = launchManager.getLaunches(); ILaunch launch = null; for (int i = 0; launch == null && i < launches.length; ++i) { IProcess[] processes = launches[i].getProcesses(); for (IProcess iProcess : processes) { String commandLine = iProcess.getAttribute(IProcess.ATTR_CMDLINE); if (commandLine != null && commandLine.indexOf(clientId) != -1) { launch = launches[i]; break; } } } WebAppDebugModel model = WebAppDebugModel.getInstance(); LaunchConfiguration lc = model.addOrReturnExistingLaunchConfiguration(launch, clientId, null); lc.setLaunchUrls(initialize.getStartupURLsList()); setLaunchConfiguration(lc); DevModeServiceClient devModeServiceClient = new DevModeServiceClient(getTransport()); DevModeServiceClientManager.getInstance().putClient(getLaunchConfiguration(), devModeServiceClient); performDevModeServiceCapabilityExchange(devModeServiceClient); return buildResponse(null); } private void setLaunchConfiguration(LaunchConfiguration lc) { synchronized (privateInstanceLock) { this.launchConfiguration = lc; } } }