/* * Copyright (C) 2010 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.api.explorer.client.history; import com.google.api.explorer.client.base.ApiMethod.HttpMethod; import com.google.api.explorer.client.base.ApiRequest; import com.google.api.explorer.client.base.ApiResponse; import com.google.api.explorer.client.base.ApiResponse.HeaderValue; import com.google.api.explorer.client.base.Config; import com.google.api.explorer.client.base.ExplorerConfig; import com.google.api.explorer.client.base.dynamicjso.DynamicJso; import com.google.api.explorer.client.history.JsonPrettifier.JsonFormatException; import com.google.api.explorer.client.history.JsonPrettifier.PrettifierLinkFactory; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsonUtils; import com.google.gwt.dom.client.PreElement; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; import com.google.gwt.resources.client.CssResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import java.util.Date; import java.util.Map; import java.util.SortedMap; public class EmbeddedHistoryItemView extends Composite { private static HistoryItemUiBinder uiBinder = GWT.create(HistoryItemUiBinder.class); private static final String IMAGE_TYPE_PREFIX = "image/"; private static final String TEXT_TYPE_PREFIX = "text/"; private static final String CONTENT_TYPE_HEADER = "content-type"; private static final String AUTH_HEADER = "authorization"; interface HistoryItemUiBinder extends UiBinder<Widget, EmbeddedHistoryItemView> { } interface EmbeddedHistoryItemViewStyle extends CssResource { String fadeIn(); } @UiField public Panel titleBar; @UiField public SpanElement title; @UiField public SpanElement time; @UiField public SimplePanel errorPanel; @UiField public PreElement requestDiv; @UiField public FlowPanel requestBodyDiv; @UiField public PreElement statusDiv; @UiField public Label showHideHeaders; @UiField public PreElement responseHeadersDiv; @UiField public FlowPanel responseBodyDiv; @UiField public Panel executing; @UiField public HTMLPanel wireContent; @UiField EmbeddedHistoryItemViewStyle style; private final ApiRequest request; private final String realPathFragment; public EmbeddedHistoryItemView(ApiRequest request) { initWidget(); this.request = request; // Stash the real URL in case we need it for loading media realPathFragment = request.getRequestPath(); // Replace the API key with a fake version if the default was used if (ExplorerConfig.API_KEY.equals(request.getApiKey())) { request.setApiKey("{YOUR_API_KEY}"); } String prefix = request.getMethod().getId() + " executed "; PrettyDate.keepMakingPretty(new Date(), prefix, title); String dateString = DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_SHORT).format(new Date()); title.setTitle(dateString); requestDiv.setInnerText(getRequestString(request)); } /** * Complete the partially filled history item with the response data. * * @param response Response data. * @param timeMillis Time that execution took in milliseconds. * @param linkFactory Link factory that is used to generate hyperlink and menu links in the * response view. */ public void complete(ApiResponse response, long timeMillis, PrettifierLinkFactory linkFactory) { executing.setVisible(false); wireContent.addStyleName(style.fadeIn()); time.setInnerText("time to execute: " + timeMillis + " ms"); statusDiv.setInnerText(response.getStatus() + " " + response.getStatusText()); // Headers are hidden by default. UIObject.setVisible(responseHeadersDiv, false); responseHeadersDiv.setInnerText(getResponseHeadersString(response)); try { JsonPrettifier.prettify( request.getService(), requestBodyDiv, request.getRequestBody(), linkFactory); } catch (JsonFormatException e) { // We should only be generating valid requests requestBodyDiv.add(new InlineLabel(request.getRequestBody())); } setResponseContent(request, response, realPathFragment, linkFactory); } /** * Set the value of the panel reserved for the formatted response. There are a couple different * scenarios to tackle. If we can determine that the request returned an image, and the request is * repeatable, we will create an image tag with a source of the original request. * * If the response is a non-JSON text type, we just show it directly. * * In all other cases we try to process the text as JSON and if for some reason that fails, we * just hide it under an opaque tag that says as much information as we know about the response. * * @param request Request object with the API key replaced. * @param response Response from the server. * @param originalPath Path object before we replaced the API key. * @param linkFactory Which links factory should be used when generating links and navigation * menus. */ private void setResponseContent(ApiRequest request, ApiResponse response, String originalPath, PrettifierLinkFactory linkFactory) { HeaderValue authorization = response.getHeaders().get(AUTH_HEADER); HeaderValue contentTypeHeader = response.getHeaders().get(CONTENT_TYPE_HEADER); GWT.log("Headers: " + response.getHeaders().entrySet()); String contentType = contentTypeHeader == null ? "Unspecified" : contentTypeHeader.getValue(); if (request.getHttpMethod() == HttpMethod.GET && contentType.startsWith(IMAGE_TYPE_PREFIX) && authorization == null) { // In the very special case that we performed a get and were given an // image, display it Image img = new Image(); img.setUrl(Config.getBaseUrl() + originalPath); img.setAltText(Config.getBaseUrl() + request.getRequestPath()); responseBodyDiv.add(img); } else if (contentType.startsWith(TEXT_TYPE_PREFIX)) { // We have non-JSON text, just show it. responseBodyDiv.add(new Label(response.getBodyAsString())); } else { // Treat the response as JSON, although we don't really know what it is try { JsonPrettifier.prettify( request.getService(), responseBodyDiv, response.getBodyAsString(), linkFactory); } catch (JsonFormatException e) { // If JSON processing fails, just say what we know about the data responseBodyDiv.add(new Label("[" + contentType + " data]")); } // Check if there was an error, and, if so, display it to the user. ErrorCase error = getErrorMessage(response); if (error != null) { setErrorMessage(error.getErrorLabel()); } } } protected void initWidget() { initWidget(uiBinder.createAndBindUi(this)); } @UiHandler("showHideHeaders") public void showHide(ClickEvent event) { showHideHeaders.setText( UIObject.isVisible(responseHeadersDiv) ? "- Show headers -" : "- Hide headers -"); UIObject.setVisible(responseHeadersDiv, !UIObject.isVisible(responseHeadersDiv)); } private static String getRequestString(ApiRequest request) { StringBuilder sb = new StringBuilder() .append(request.getHttpMethod().name()) .append(' ') .append(Config.getBaseUrl()) // If the standard API key is being used, mask it in the UI. // The URL is already URL-escaped before making the request, so we don't // want to double-escape it. .append(request.getRequestPath()); // Display headers that were set on the request. // TODO(jasonhall): This can be prettier. sb.append('\n'); for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) { sb.append('\n').append(entry.getKey()).append(": ").append(entry.getValue()); } return sb.toString(); } private static ErrorCase getErrorMessage(ApiResponse response) { // This requires a try-catch because there is no way to proactively check // that the JSON is both present and valid without just trying to parse it. try { DynamicJso jso = JsonUtils.safeEval(response.getBodyAsString()); if (jso.get("error") != null) { return ErrorCase.forJsonString(response.getBodyAsString()); } } catch (IllegalArgumentException e) { // Not valid json, definitely not an error payload. } return null; } private static String getResponseHeadersString(ApiResponse response) { StringBuilder sb = new StringBuilder(); SortedMap<String, HeaderValue> sorted = Maps.newTreeMap(Ordering.natural()); sorted.putAll(response.getHeaders()); for (HeaderValue header : sorted.values()) { sb.append(header.getKey()).append(": ").append(header.getValue()).append('\n'); } return sb.toString(); } private void setErrorMessage(Widget prettyMessage) { errorPanel.setVisible(true); errorPanel.add(prettyMessage); } }