| /* |
| * Copyright 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.dev.shell.remoteui; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.TreeLogger.HelpInfo; |
| import com.google.gwt.core.ext.TreeLogger.Type; |
| import com.google.gwt.dev.protobuf.ByteString; |
| import com.google.gwt.dev.shell.remoteui.MessageTransport.RequestException; |
| import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request; |
| import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Response; |
| import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest; |
| import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest.LogData; |
| import com.google.gwt.dev.shell.remoteui.RemoteMessageProto.Message.Request.ViewerRequest.RequestType; |
| 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.util.Callback; |
| import com.google.gwt.dev.util.log.AbstractTreeLogger; |
| |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Future; |
| |
| /** |
| * Used for making requests to a remote ViewerService server. |
| * |
| * TODO: If this becomes part of the public API, we'll need to provide a level |
| * of indirection in front of the protobuf classes; We're going to be rebasing |
| * the protobuf library, and we don't want to expose the rebased API as public. |
| */ |
| public class ViewerServiceClient { |
| |
| private final MessageTransport transport; |
| private final TreeLogger unexpectedErrorLogger; |
| |
| /** |
| * Create a new instance. |
| * |
| * @param processor A MessageProcessor that is used to communicate with the |
| * ViewerService server. |
| */ |
| public ViewerServiceClient(MessageTransport processor, |
| TreeLogger unexpectedErrorLogger) { |
| this.transport = processor; |
| this.unexpectedErrorLogger = unexpectedErrorLogger; |
| } |
| |
| /** |
| * Add an entry that also serves as a log branch. |
| * |
| * @param indexInParent The index of this entry/branch within the parent |
| * logger |
| * @param type The severity of the log message. |
| * @param msg The message. |
| * @param caught An exception associated with the message |
| * @param helpInfo A URL or message which directs the user to helpful |
| * information related to the log message |
| * @param parentLogHandle The log handle of the parent of this log |
| * entry/branch |
| * @param callback the callback to call when a branch handle is available |
| */ |
| public void addLogBranch(int indexInParent, Type type, String msg, |
| Throwable caught, HelpInfo helpInfo, int parentLogHandle, |
| final Callback<Integer> callback) { |
| |
| LogData.Builder logDataBuilder = generateLogData(type, msg, caught, |
| helpInfo); |
| |
| ViewerRequest.AddLogBranch.Builder addlogBranchBuilder = ViewerRequest.AddLogBranch.newBuilder(); |
| addlogBranchBuilder.setParentLogHandle(parentLogHandle); |
| addlogBranchBuilder.setIndexInParent(indexInParent); |
| addlogBranchBuilder.setLogData(logDataBuilder); |
| |
| ViewerRequest.Builder viewerRequestBuilder = ViewerRequest.newBuilder(); |
| viewerRequestBuilder.setRequestType(ViewerRequest.RequestType.ADD_LOG_BRANCH); |
| viewerRequestBuilder.setAddLogBranch(addlogBranchBuilder); |
| |
| Request requestMessage = buildRequestMessageFromViewerRequest( |
| viewerRequestBuilder).build(); |
| |
| transport.executeRequestAsync(requestMessage, new Callback<Response>() { |
| public void onDone(Response result) { |
| callback.onDone(result.getViewerResponse().getAddLogBranch().getLogHandle()); |
| } |
| |
| public void onError(Throwable t) { |
| callback.onError(t); |
| } |
| }); |
| } |
| |
| /** |
| * Add a log entry. |
| * |
| * @param indexInParent The index of this entry within the parent logger |
| * @param type The severity of the log message. |
| * @param msg The message. |
| * @param caught An exception associated with the message |
| * @param helpInfo A URL or message which directs the user to helpful |
| * information related to the log message |
| * @param logHandle The log handle of the parent of this log entry/branch |
| */ |
| public void addLogEntry(int indexInParent, Type type, String msg, |
| Throwable caught, HelpInfo helpInfo, int logHandle) { |
| LogData.Builder logDataBuilder = generateLogData(type, msg, caught, |
| helpInfo); |
| |
| ViewerRequest.AddLogEntry.Builder addLogEntryBuilder = ViewerRequest.AddLogEntry.newBuilder(); |
| addLogEntryBuilder.setLogHandle(logHandle); |
| addLogEntryBuilder.setIndexInLog(indexInParent); |
| addLogEntryBuilder.setLogData(logDataBuilder); |
| |
| ViewerRequest.Builder viewerRequestBuilder = ViewerRequest.newBuilder(); |
| viewerRequestBuilder.setRequestType(ViewerRequest.RequestType.ADD_LOG_ENTRY); |
| viewerRequestBuilder.setAddLogEntry(addLogEntryBuilder); |
| |
| Request requestMessage = buildRequestMessageFromViewerRequest( |
| viewerRequestBuilder).build(); |
| |
| transport.executeRequestAsync(requestMessage, new Callback<Response>() { |
| public void onDone(Response result) { |
| } |
| |
| public void onError(Throwable t) { |
| unexpectedErrorLogger.log(TreeLogger.WARN, |
| "An error occurred while attempting to add a log entry.", t); |
| } |
| }); |
| } |
| |
| /** |
| * Add a new Module logger. This method should not be called multiple times |
| * with the exact same arguments (as there should only be one logger |
| * associated with that set of arguments). |
| * |
| * @param remoteSocket name of remote socket endpoint in host:port format |
| * @param url URL of top-level window |
| * @param tabKey stable browser tab identifier, or the empty string if no such |
| * identifier is available |
| * @param moduleName the name of the module loaded |
| * @param sessionKey a unique session key |
| * @param agentTag short-form user agent identifier, suitable for use in a |
| * label for this connection |
| * @param agentIcon icon to use for the user agent (fits inside 24x24) or null |
| * if unavailable |
| * @return the log handle for the newly-created Module logger |
| */ |
| public int addModuleLog(String remoteSocket, String url, String tabKey, |
| String moduleName, String sessionKey, String agentTag, byte[] agentIcon) { |
| ViewerRequest.AddLog.ModuleLog.Builder moduleLogBuilder = ViewerRequest.AddLog.ModuleLog.newBuilder(); |
| moduleLogBuilder.setName(moduleName); |
| moduleLogBuilder.setUserAgent(agentTag); |
| |
| if (url != null) { |
| moduleLogBuilder.setUrl(url); |
| } |
| |
| moduleLogBuilder.setRemoteHost(remoteSocket); |
| moduleLogBuilder.setSessionKey(sessionKey); |
| |
| if (tabKey != null) { |
| moduleLogBuilder.setTabKey(tabKey); |
| } |
| |
| if (agentIcon != null) { |
| moduleLogBuilder = moduleLogBuilder.setIcon(ByteString.copyFrom(agentIcon)); |
| } |
| |
| ViewerRequest.AddLog.Builder addLogBuilder = ViewerRequest.AddLog.newBuilder(); |
| addLogBuilder.setType(ViewerRequest.AddLog.LogType.MODULE); |
| addLogBuilder.setModuleLog(moduleLogBuilder); |
| |
| return createLogger(addLogBuilder); |
| } |
| |
| /** |
| * Check the capabilities of the ViewerService. Ensures that the ViewerService |
| * supports: adding a log, adding a log branch, adding a log entry, and |
| * disconnecting a log. |
| * |
| * TODO: Should we be checking the specific capability of the the |
| * ViewerService to support logs of type MAIN, SERVER, and MODULE? Right now, |
| * we assume that if they can support the addition of logs, they can handle |
| * the addition of any types of logs that we throw at them. |
| */ |
| public void checkCapabilities() { |
| ViewerRequest.CapabilityExchange.Builder capabilityExchangeBuilder = ViewerRequest.CapabilityExchange.newBuilder(); |
| ViewerRequest.Builder viewerRequestBuilder = ViewerRequest.newBuilder(); |
| viewerRequestBuilder.setRequestType(ViewerRequest.RequestType.CAPABILITY_EXCHANGE); |
| viewerRequestBuilder.setCapabilityExchange(capabilityExchangeBuilder); |
| |
| Request.Builder request = buildRequestMessageFromViewerRequest(viewerRequestBuilder); |
| |
| Future<Response> responseFuture = transport.executeRequestAsync(request.build()); |
| Response response = waitForResponseOrThrowUncheckedException(responseFuture); |
| |
| ViewerResponse.CapabilityExchange capabilityExchangeResponse = response.getViewerResponse().getCapabilityExchange(); |
| List<Capability> capabilityList = capabilityExchangeResponse.getCapabilitiesList(); |
| |
| // Check for the add log ability |
| checkCapability(capabilityList, RequestType.ADD_LOG); |
| |
| // Check for the add log branch ability |
| checkCapability(capabilityList, RequestType.ADD_LOG_BRANCH); |
| |
| // Check for the add log branch ability |
| checkCapability(capabilityList, RequestType.ADD_LOG_BRANCH); |
| |
| // Check for the disconnect log capability |
| checkCapability(capabilityList, RequestType.DISCONNECT_LOG); |
| } |
| |
| /** |
| * Disconnect the log. Indicate to the log that the process which was logging |
| * messages to it is now dead, and no more messages will be logged to it. |
| * |
| * Note that the log handle should refer to a top-level log, not a branch log. |
| * |
| * @param logHandle the handle of the top-level log to disconnect |
| */ |
| public void disconnectLog(int logHandle) { |
| ViewerRequest.DisconnectLog.Builder disconnectLogbuilder = ViewerRequest.DisconnectLog.newBuilder(); |
| disconnectLogbuilder.setLogHandle(logHandle); |
| |
| ViewerRequest.Builder viewerRequestBuilder = ViewerRequest.newBuilder(); |
| viewerRequestBuilder.setRequestType(RequestType.DISCONNECT_LOG); |
| viewerRequestBuilder.setDisconnectLog(disconnectLogbuilder); |
| |
| Request.Builder request = buildRequestMessageFromViewerRequest(viewerRequestBuilder); |
| Future<Response> responseFuture = transport.executeRequestAsync(request.build()); |
| waitForResponseOrThrowUncheckedException(responseFuture); |
| } |
| |
| public void initialize(String clientId, List<String> startupURLs) { |
| ViewerRequest.Initialize.Builder initializationBuilder = ViewerRequest.Initialize.newBuilder(); |
| initializationBuilder.setClientId(clientId); |
| initializationBuilder.addAllStartupURLs(startupURLs); |
| |
| ViewerRequest.Builder viewerRequestBuilder = ViewerRequest.newBuilder(); |
| viewerRequestBuilder.setRequestType(ViewerRequest.RequestType.INITIALIZE); |
| viewerRequestBuilder.setInitialize(initializationBuilder); |
| |
| Request.Builder request = buildRequestMessageFromViewerRequest(viewerRequestBuilder); |
| |
| Future<Response> responseFuture = transport.executeRequestAsync(request.build()); |
| waitForResponseOrThrowUncheckedException(responseFuture); |
| } |
| |
| private Request.Builder buildRequestMessageFromViewerRequest( |
| ViewerRequest.Builder viewerRequestBuilder) { |
| return Request.newBuilder().setServiceType(Request.ServiceType.VIEWER).setViewerRequest( |
| viewerRequestBuilder); |
| } |
| |
| private void checkCapability(List<Capability> viewerCapabilityList, |
| RequestType capabilityWeNeed) { |
| for (Capability c : viewerCapabilityList) { |
| if (c.getCapability() == capabilityWeNeed) { |
| return; |
| } |
| } |
| throw new RuntimeException("ViewerService does not support " |
| + capabilityWeNeed.toString()); |
| } |
| |
| private int createLogger(ViewerRequest.AddLog.Builder addLogBuilder) { |
| ViewerRequest.Builder viewerRequestBuilder = ViewerRequest.newBuilder(); |
| viewerRequestBuilder.setRequestType(ViewerRequest.RequestType.ADD_LOG); |
| viewerRequestBuilder.setAddLog(addLogBuilder); |
| |
| Request.Builder request = buildRequestMessageFromViewerRequest(viewerRequestBuilder); |
| |
| Future<Response> responseFuture = transport.executeRequestAsync(request.build()); |
| return waitForResponseOrThrowUncheckedException(responseFuture).getViewerResponse().getAddLog().getLogHandle(); |
| } |
| |
| private LogData.Builder generateLogData(Type type, String msg, |
| Throwable caught, HelpInfo helpInfo) { |
| LogData.Builder logBuilder = LogData.newBuilder().setSummary(msg); |
| logBuilder.setLevel(type.getLabel()); |
| |
| if (caught != null) { |
| String stackTraceAsString = AbstractTreeLogger.getStackTraceAsString(caught); |
| if (stackTraceAsString != null) { |
| logBuilder = logBuilder.setDetails(stackTraceAsString); |
| } |
| } |
| |
| if (helpInfo != null) { |
| LogData.HelpInfo.Builder helpInfoBuilder = LogData.HelpInfo.newBuilder(); |
| |
| if (helpInfo.getURL() != null) { |
| helpInfoBuilder.setUrl(helpInfo.getURL().toExternalForm()); |
| } |
| |
| if (helpInfo.getAnchorText() != null) { |
| helpInfoBuilder.setText(helpInfo.getAnchorText()); |
| } |
| |
| logBuilder.setHelpInfo(helpInfoBuilder); |
| } |
| |
| if (type.needsAttention()) { |
| // Set this field if attention is actually needed |
| logBuilder.setNeedsAttention(true); |
| } |
| |
| return logBuilder; |
| } |
| |
| /** |
| * Waits for response and throws a checked exception if the request failed. |
| * |
| * Requests can fail if the other side does not understand the message -- for |
| * example, if it is running an older version. |
| * |
| * @throws RequestException if the request failed |
| */ |
| private Response waitForResponse(Future<Response> future) |
| throws RequestException { |
| try { |
| return future.get(); |
| } catch (ExecutionException e) { |
| Throwable cause = e.getCause(); |
| if (cause instanceof RequestException) { |
| throw (RequestException) cause; |
| } else { |
| throw new RuntimeException(e); |
| } |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Waits for response and throws an unchecked exception if the request failed. |
| */ |
| private Response waitForResponseOrThrowUncheckedException( |
| Future<Response> future) { |
| try { |
| return waitForResponse(future); |
| } catch (RequestException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |