com.google.api.explorer.client.FullView.java Source code

Java tutorial

Introduction

Here is the source code for com.google.api.explorer.client.FullView.java

Source

/*
 * 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;

import com.google.api.explorer.client.FullViewPresenter.NavigationItem;
import com.google.api.explorer.client.analytics.AnalyticsManager;
import com.google.api.explorer.client.auth.AuthView;
import com.google.api.explorer.client.base.ApiDirectory.ServiceDefinition;
import com.google.api.explorer.client.base.ApiMethod;
import com.google.api.explorer.client.base.ApiRequest;
import com.google.api.explorer.client.base.ApiResponse;
import com.google.api.explorer.client.base.ApiService;
import com.google.api.explorer.client.base.NameHelper;
import com.google.api.explorer.client.context.ExplorerContext;
import com.google.api.explorer.client.context.ListServiceContext.TagProcessor;
import com.google.api.explorer.client.embedded.EmbeddedParameterFormPresenter.RequestFinishedCallback;
import com.google.api.explorer.client.embedded.EmbeddedView;
import com.google.api.explorer.client.history.EmbeddedHistoryItemView;
import com.google.api.explorer.client.history.HistoryItem;
import com.google.api.explorer.client.history.JsonPrettifier;
import com.google.api.explorer.client.navigation.EntryAggregatorView;
import com.google.api.explorer.client.navigation.HistoryEntry;
import com.google.api.explorer.client.navigation.MethodEntry;
import com.google.api.explorer.client.navigation.SectionedAggregator;
import com.google.api.explorer.client.navigation.ServiceEntry;
import com.google.api.explorer.client.navigation.ServiceEntry.DescriptionTag;
import com.google.api.explorer.client.routing.TitleSupplier.Title;
import com.google.api.explorer.client.routing.URLManipulator;
import com.google.api.explorer.client.routing.UrlBuilder.RootNavigationItem;
import com.google.api.explorer.client.routing.handler.HistoryManager.HistoryManagerDelegate;
import com.google.api.explorer.client.search.SearchManager.SearchReadyCallback;
import com.google.api.explorer.client.search.SearchResult;
import com.google.api.explorer.client.search.SearchResult.MethodBundle;
import com.google.api.explorer.client.widgets.PlaceholderTextBox;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.logical.shared.SelectionEvent;
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.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineHyperlink;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
import com.google.gwt.user.client.ui.SuggestBox.SuggestionDisplay;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.Widget;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

/**
 * View of the whole app.
 *
 * @author jasonhall@google.com (Jason Hall)
 */
public class FullView extends Composite
        implements FullViewPresenter.Display, HistoryManagerDelegate, SearchReadyCallback {

    private static FullViewUiBinder uiBinder = GWT.create(FullViewUiBinder.class);

    private static final String REPORT_ERROR_URL = "http://code.google.com/p/google-apis-explorer/"
            + "issues/entry?template=Defect%20report%20from%20user";
    private static final String EXPLORER_HELP_URL = "http://code.google.com/apis/explorer-help";
    private static final String EXPLORER_FORUM_URL = "http://code.google.com/apis/explorer-help/forum.html";

    private static final String NEW_TAB_TARGET = "_blank";
    private static final String SETTINGS_MENU_CSS_RULE = "settingsMenu";
    private static final boolean HIDE_AUTH = false;

    interface FullViewUiBinder extends UiBinder<Widget, FullView> {
    }

    interface FullViewStyle extends CssResource {
        String selectedNavigation();

        String searchPlaceholderText();

        String methodSubtitle();
    }

    @UiField
    FullViewStyle style;

    @UiField
    DockLayoutPanel dockLayoutPanel;
    @UiField
    Image logo;
    @UiField
    PushButton backButton;

    @UiField
    Widget searchLoadingIndicator;
    @UiField(provided = true)
    SuggestBox searchBox;
    @UiField
    Panel searchErrorPanel;

    @UiField
    SectionedAggregator searchResults;

    @UiField
    EntryAggregatorView drillDownNav;

    @UiField
    Panel detailHeader;
    @UiField
    Panel detailTitleContainer;
    @UiField
    Panel authViewPlaceholder;

    @UiField
    Panel docsContainer;

    @UiField
    Panel detailPane;

    @UiField
    Panel preferredServicesMenuItem;
    @UiField
    Panel requestHistoryMenuItem;
    @UiField
    Panel allServicesMenuItem;

    @UiField
    MenuBar settingsMenu;
    @UiField
    MenuItem helpItem;
    @UiField
    MenuItem forumItem;
    @UiField
    MenuItem bugReportItem;

    private final FullViewPresenter presenter;
    private final AuthManager authManager;
    private final AnalyticsManager analytics;

    public FullView(URLManipulator urlManipulator, AuthManager authManager, AnalyticsManager analytics,
            SuggestOracle searchKeywords) {

        this.analytics = analytics;
        this.presenter = new FullViewPresenter(urlManipulator, this);
        this.authManager = authManager;
        PlaceholderTextBox searchBackingTextBox = new PlaceholderTextBox(
                "Search for services, methods, and recent requests...");
        this.searchBox = new SuggestBox(searchKeywords, searchBackingTextBox);

        searchBox.setAutoSelectEnabled(false);
        initWidget(uiBinder.createAndBindUi(this));
        setMenuActions();

        // Add a fixed css class name that I can use to be able to style the menu.
        settingsMenu.setStyleName(SETTINGS_MENU_CSS_RULE + " " + settingsMenu.getStyleName());

        // Set the style of the search box.
        searchBackingTextBox.setPlaceholderTextStyleName(style.searchPlaceholderText());
    }

    /**
     * Assign the actions to the settings menu items.
     */
    private void setMenuActions() {
        bugReportItem.setCommand(getOpenUrlAction(REPORT_ERROR_URL));
        helpItem.setCommand(getOpenUrlAction(EXPLORER_HELP_URL));
        forumItem.setCommand(getOpenUrlAction(EXPLORER_FORUM_URL));
    }

    /**
     * Create a command that can be bound to a menu item that will open a url in a new tab.
     */
    private Command getOpenUrlAction(final String url) {
        return new Command() {
            @Override
            public void execute() {
                Window.open(url, NEW_TAB_TARGET, "");
            }
        };
    }

    @UiHandler("preferredServicesMenuItem")
    void clickPreferred(ClickEvent event) {
        presenter.clickNavigationItem(NavigationItem.PREFERRED_SERVICES);
    }

    @UiHandler("requestHistoryMenuItem")
    void clickHistory(ClickEvent event) {
        presenter.clickNavigationItem(NavigationItem.REQUEST_HISTORY);
    }

    @UiHandler("allServicesMenuItem")
    void clickAllVersions(ClickEvent event) {
        presenter.clickNavigationItem(NavigationItem.ALL_VERSIONS);
    }

    @UiHandler("logo")
    void clickLogo(ClickEvent event) {
        // Go back to the "home" state of the app when the logo is clicked.
        presenter.handleClickLogo();
    }

    @UiHandler("backButton")
    void clickBack(ClickEvent event) {
        presenter.handleClickBack();
    }

    @UiHandler("searchButton")
    void clickSearch(ClickEvent event) {
        presenter.handleSearch(searchBox.getText());
    }

    @UiHandler("searchBox")
    void searchBoxEnter(KeyDownEvent event) {
        if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
            SuggestionDisplay suggestionDisplay = searchBox.getSuggestionDisplay();

            // This should always be true unless GWT changes the type of the suggestion generated by the
            // SuggestBox. It is too complicated and nasty to switch out the SuggestBox suggestion display
            // factory, so we're left with this type safety check and broken functionality if GWT changes.
            Preconditions.checkState(suggestionDisplay instanceof DefaultSuggestionDisplay);

            // At this point this should always be true.
            if (suggestionDisplay instanceof DefaultSuggestionDisplay) {
                DefaultSuggestionDisplay suggestions = (DefaultSuggestionDisplay) suggestionDisplay;
                if (!suggestions.isSuggestionListShowing()) {
                    presenter.handleSearch(searchBox.getValue());
                }
            }
        }
    }

    @UiHandler("searchBox")
    void suggestionSelected(SelectionEvent<Suggestion> event) {
        presenter.handleSearch(event.getSelectedItem().getReplacementString());
    }

    @Override
    public void setContext(ExplorerContext context) {
        presenter.setContext(context);

        // Fill in the entry list widget, only the collections that have entries will be shown
        drillDownNav.setVisible(context.isEntryListVisible());
        drillDownNav.clear();

        if (context.isEntryListVisible()) {
            populateHistoryItems("", context.getHistoryItems(), drillDownNav);
            populateServiceEntries(sortServices(context.getServicesList()), drillDownNav,
                    context.getServiceTagProcessor());
            populateServiceMethods(context.getService(), context.getMethods(), drillDownNav);
        }

        // Fill in the detail pane.
        detailPane.setVisible(context.isHistoryItemVisible() || context.isMethodFormVisible());
        detailPane.clear();

        if (context.isHistoryItemVisible()) {
            HistoryItem item = Iterables.getOnlyElement(context.getHistoryItems());
            EmbeddedHistoryItemView view = generateHistoryItemView(item);

            detailPane.add(view);
        } else if (context.isMethodFormVisible()) {
            ApiMethod method = context.getMethod();

            // Wrap the callback given by the context so that we may also be notified when a request is
            // finished. Pass through events to the original callback.
            CallbackWrapper cbWrapper = new CallbackWrapper();
            cbWrapper.delegate = context.getRequestFinishedCallback();
            cbWrapper.methodName = method.getId();

            // Create the view of the request editor and the single history item.
            EmbeddedView view = new EmbeddedView(authManager, context.getService(), method,
                    context.getMethodParameters(), cbWrapper, HIDE_AUTH, analytics);

            cbWrapper.localView = view;

            // If this context came bundled with a history item, that means the navigation references a
            // previous executed request, and we should show the result.
            List<HistoryItem> historyItems = context.getHistoryItems();
            if (!historyItems.isEmpty()) {
                view.showHistoryItem(generateHistoryItemView(Iterables.getLast(historyItems)));
            }

            detailPane.add(view);
        }

        // Show the search results.
        searchResults.setVisible(context.isSearchResultsVisible());
        searchResults.clear();
        searchErrorPanel.setVisible(false);
        if (context.isSearchResultsVisible()) {
            populateSearchResults(context.getSearchResults(), context.getServiceTagProcessor());
        }

        // Show the auth panel.
        authViewPlaceholder.setVisible(context.isAuthVisible());
        authViewPlaceholder.clear();
        if (context.isAuthVisible()) {
            showAuth(context.getService(), context.getMethod());
        }

        // Show the documentation link.
        docsContainer.setVisible(context.isDocsLinkVisible());
        docsContainer.clear();
        if (context.isDocsLinkVisible()) {
            showDocumentationLink("the " + context.getService().displayTitle(),
                    context.getService().getDocumentationLink());
        }

        // Show the title.
        boolean showContentTitle = context.getContentTitles() != null;
        if (showContentTitle) {
            generateBreadcrumbs(detailTitleContainer, context.getContentTitles());
        }

        // Show the detail header.
        detailHeader.setVisible(showContentTitle || context.isAuthVisible());

        // Show the back button.
        backButton.setVisible(context.getParentUrl() != null);

        // Highlight the navigation item which was the root of our navigation.
        highlightNavigationItem(context.getRootNavigationItem());
    }

    /**
     * Generate a view of the provided history item.
     */
    private EmbeddedHistoryItemView generateHistoryItemView(HistoryItem item) {
        EmbeddedHistoryItemView view = new EmbeddedHistoryItemView(item.getRequest());
        view.complete(item.getResponse(), item.getEndTime() - item.getStartTime(),
                JsonPrettifier.LOCAL_LINK_FACTORY);
        return view;
    }

    /**
     * Generate breadcrumbs into the specified container using the format link > link > text where the
     * last breadcrumb is always plain text.
     */
    private void generateBreadcrumbs(Panel container, List<Title> titles) {
        container.clear();

        // For all of the titles previous to the last, add a link and a separator.
        for (Title notLast : titles.subList(0, titles.size() - 1)) {
            container.add(new InlineHyperlink(notLast.getTitle(), notLast.getFragment()));
            container.add(new InlineLabel(" > "));
        }

        // Append only the text for the last title.
        Title lastTitle = Iterables.getLast(titles);
        container.add(new InlineLabel(lastTitle.getTitle()));
        if (lastTitle.getSubtitle() != null) {
            Label subtitle = new InlineLabel(" - " + lastTitle.getSubtitle());
            subtitle.addStyleName(style.methodSubtitle());
            container.add(subtitle);
        }
    }

    private void showAuth(ApiService service, ApiMethod method) {
        AuthView auth = new AuthView(authManager, service, analytics);

        if (method != null) {
            auth.getPresenter().setStateForMethod(method);
        }

        authViewPlaceholder.add(auth);
    }

    private void showDocumentationLink(String componentName, String href) {
        docsContainer.add(new InlineLabel("Learn more about using " + componentName + " by reading the "));
        docsContainer.add(new Anchor("documentation", href, NEW_TAB_TARGET));
        docsContainer.add(new InlineLabel("."));
    }

    /**
     * Display the specified service entries in the container provided, while applying the tags
     * generated by the tag processor.
     */
    private void populateServiceEntries(Iterable<ServiceDefinition> services, EntryAggregatorView toPopulate,
            Set<TagProcessor> tagProcessors) {

        for (final ServiceDefinition service : services) {
            String iconUrl = service.getIcons().getIcon16Url();
            String displayName = NameHelper.generateDisplayTitle(service.getTitle(), service.getName());

            Set<DescriptionTag> tags = Sets.newHashSet();
            for (TagProcessor processor : tagProcessors) {
                tags.addAll(processor.process(service));
            }

            HasClickHandlers rowHandle = toPopulate.addEntry(
                    new ServiceEntry(iconUrl, displayName, service.getVersion(), service.getDescription(), tags));
            rowHandle.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    presenter.handleClickService(service);
                }
            });
        }
    }

    /**
     * Display the spcified history items in the aggregator specified.
     *
     * @param prefix Prefix that should be prepended to the history item URL when an item is clicked,
     *        changes based on whether this was a search result or the history item list.
     * @param historyItems Items which to render and display in the aggregator,
     * @param aggregator Aggregator that will display rendered history items.
     */
    private void populateHistoryItems(final String prefix, Iterable<HistoryItem> historyItems,
            EntryAggregatorView aggregator) {

        for (final HistoryItem item : historyItems) {
            ApiRequest request = item.getRequest();
            HasClickHandlers rowHandler = aggregator.addEntry(new HistoryEntry(request.getMethod().getId(),
                    request.getHttpMethod().toString() + " " + request.getRequestPath(), item.getEndTime()));
            rowHandler.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    presenter.handleClickHistoryItem(prefix, item);
                }
            });
        }
    }

    /**
     * Display all of the methods for the specified service in the aggregator provided.
     */
    private void populateServiceMethods(ApiService service, Iterable<ApiMethod> methods, EntryAggregatorView view) {

        for (final ApiMethod method : methods) {
            populateMethodEntry(method, null, "", view);
        }
    }

    /**
     * Add an aggregator line for the particular method specified. When clicked, append the prefix
     * specified and then the method identifier to the current URL.
     */
    private void populateMethodEntry(final ApiMethod method, @Nullable String serviceTitle, final String prefix,
            EntryAggregatorView aggregator) {

        HasClickHandlers rowHandler = aggregator
                .addEntry(new MethodEntry(method.getId(), serviceTitle, method.getDescription()));
        rowHandler.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent arg0) {
                presenter.handleClickMethod(prefix, method);
            }
        });
    }

    /**
     * Take the list of search results and split them into appropriate aggregators hidden under
     * disclosure panels.
     */
    private void populateSearchResults(Iterable<SearchResult> results, Set<TagProcessor> serviceTagProcessors) {
        List<MethodBundle> methodResults = Lists.newArrayList();
        List<ServiceDefinition> serviceResults = Lists.newArrayList();
        List<HistoryItem> historyResults = Lists.newArrayList();

        for (SearchResult result : results) {
            switch (result.getKind()) {
            case HISTORY_ITEM:
                historyResults.add(result.getHistoryItem());
                break;

            case METHOD:
                methodResults.add(result.getMethodBundle());
                break;

            case SERVICE:
                serviceResults.add(result.getService());
                break;

            default:
                throw new RuntimeException("Unknown search result type: " + result.toString());
            }
        }

        if (!serviceResults.isEmpty()) {
            EntryAggregatorView serviceAggregator = new EntryAggregatorView();

            populateServiceEntries(serviceResults, serviceAggregator, serviceTagProcessors);

            searchResults.addSection("Services", serviceAggregator);
        }

        if (!methodResults.isEmpty()) {
            EntryAggregatorView methodAggregator = new EntryAggregatorView();

            for (MethodBundle bundle : methodResults) {
                String prefix = "m/" + bundle.getService().getName() + "/" + bundle.getService().getVersion() + "/";
                String serviceTitle = bundle.getService().displayTitle() + " " + bundle.getService().getVersion();
                populateMethodEntry(bundle.getMethod(), serviceTitle, prefix, methodAggregator);
            }

            searchResults.addSection("Methods", methodAggregator);
        }

        if (!historyResults.isEmpty()) {
            EntryAggregatorView historyAggregator = new EntryAggregatorView();
            populateHistoryItems("h/", historyResults, historyAggregator);
            searchResults.addSection("History", historyAggregator);
        }

        if (serviceResults.isEmpty() && methodResults.isEmpty() && historyResults.isEmpty()) {
            // There are no results, show the message
            searchResults.setVisible(false);
            searchErrorPanel.setVisible(true);
        }
    }

    /**
     * Highlight the navigation item for the root navigation item specified.
     */
    private void highlightNavigationItem(RootNavigationItem navItem) {
        preferredServicesMenuItem.removeStyleName(style.selectedNavigation());
        requestHistoryMenuItem.removeStyleName(style.selectedNavigation());
        allServicesMenuItem.removeStyleName(style.selectedNavigation());

        switch (navItem) {
        case ALL_VERSIONS:
            allServicesMenuItem.addStyleName(style.selectedNavigation());
            break;

        case PREFERRED_SERVICES:
            preferredServicesMenuItem.addStyleName(style.selectedNavigation());
            break;

        case REQUEST_HISTORY:
            requestHistoryMenuItem.addStyleName(style.selectedNavigation());
            break;
        }
    }

    @Override
    public void hideSearchLoadingIndicator() {
        searchLoadingIndicator.setVisible(false);
    }

    @Override
    public void searchReady() {
        // Delegate to the presenter
        presenter.searchReady();
    }

    private List<ServiceDefinition> sortServices(Set<ServiceDefinition> services) {
        List<ServiceDefinition> serviceList = Lists.newArrayList(services);
        Collections.sort(serviceList, new Comparator<ServiceDefinition>() {
            @Override
            public int compare(ServiceDefinition s1, ServiceDefinition s2) {
                String s1Title = NameHelper.generateDisplayTitle(s1.getTitle(), s1.getName());
                String s2Title = NameHelper.generateDisplayTitle(s2.getTitle(), s2.getName());
                return s1Title.toLowerCase().compareTo(s2Title.toLowerCase());
            }
        });
        return Collections.unmodifiableList(serviceList);
    }

    /**
     * Wrapper class that is used to siphon off request complete events, while still passing the
     * original events through to the wrapped delegate class.
     */
    private static class CallbackWrapper implements RequestFinishedCallback {
        public RequestFinishedCallback delegate;
        public EmbeddedView localView;
        public String methodName;

        private Map<ApiRequest, EmbeddedHistoryItemView> incompleteRequests = Maps.newHashMap();

        @Override
        public void finished(ApiRequest request, ApiResponse response, long startTime, long endTime) {
            EmbeddedHistoryItemView toComplete = incompleteRequests.get(request);
            toComplete.complete(response, endTime - startTime, JsonPrettifier.LOCAL_LINK_FACTORY);
            incompleteRequests.remove(request);

            delegate.finished(request, response, startTime, endTime);
        }

        @Override
        public void starting(ApiRequest request) {
            EmbeddedHistoryItemView incomplete = new EmbeddedHistoryItemView(request);
            incompleteRequests.put(request, incomplete);
            localView.showHistoryItem(incomplete);

            delegate.starting(request);
        }
    }
}