blob: ee268976f4fc7c0c3a2964516e011d9639ae1d83 [file] [log] [blame]
/*
* 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);
}
}
}