com.google.api.explorer.client.auth.AuthView.java Source code

Java tutorial

Introduction

Here is the source code for com.google.api.explorer.client.auth.AuthView.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.auth;

import com.google.api.explorer.client.AuthManager;
import com.google.api.explorer.client.analytics.AnalyticsManager;
import com.google.api.explorer.client.base.ApiService;
import com.google.api.explorer.client.base.ApiService.AuthScope;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
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.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
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.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusWidget;
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.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.Widget;

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

/**
 * View for Authentication status and authentication link.
 *
 * @author jasonhall@google.com (Jason Hall)
 */
public class AuthView extends Composite implements AuthPresenter.Display {
    private static AuthUiBinder uiBinder = GWT.create(AuthUiBinder.class);

    interface AuthUiBinder extends UiBinder<Widget, AuthView> {
    }

    interface AuthViewStyle extends CssResource {
        String clickable();

        String discoveryScopeSelector();
    }

    @UiField
    AuthViewStyle style;

    @UiField
    Panel scopeSelector;
    @UiField
    ToggleButton authToggle;

    @UiField
    Image authInfoIcon;
    @UiField
    Image authWarningIcon;
    @UiField
    Image authErrorIcon;

    @UiField
    Panel discloseScopeInfo;
    @UiField
    PopupPanel scopeInfoPopup;
    @UiField
    Label authMessage;
    @UiField
    Label scopeList;

    @UiField
    PopupPanel scopePopup;
    @UiField
    Panel scopePanel;
    @UiField
    Panel additionalScopePanel;
    @UiField
    Label hasScopesText;
    @UiField
    Label noScopesText;
    @UiField
    Label optionalAdditionalScopes;

    @UiField
    Label serviceName;

    @UiField
    Button authorizeButton;
    @UiField
    Button cancelAuthButton;

    private final AuthPresenter presenter;
    private Map<String, AuthScope> scopesFromDiscovery = Maps.newHashMap();
    private Set<String> selectedScopes = Sets.newLinkedHashSet();
    private List<TextBox> freeFormEditors = Lists.newLinkedList();

    /**
     * Variable to track when it was a click that was used to disclose the scope info rather than a
     * hover. This will prevent the widget from closing when the user moves the mouse away.
     */
    private boolean clickedToDiscloseScopeInfo = false;

    public AuthView(AuthManager authManager, ApiService service, AnalyticsManager analytics) {
        initWidget(uiBinder.createAndBindUi(this));

        this.presenter = new AuthPresenter(service, authManager, analytics, this);

        serviceName.setText(service.displayTitle());

        // Unless you show then hide popup windows they do not initialize properly.
        scopePopup.show();
        scopePopup.hide();

        scopeInfoPopup.show();
        scopeInfoPopup.hide();

        scopeInfoPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
            @Override
            public void onClose(CloseEvent<PopupPanel> event) {
                GWT.log("Handler for closing popup.");
                clickedToDiscloseScopeInfo = false;
            }
        });
    }

    public AuthPresenter getPresenter() {
        return presenter;
    }

    @UiHandler("authToggle")
    void authToggled(ValueChangeEvent<Boolean> event) {
        if (event.getValue()) {
            presenter.clickEnableAuth();
        } else {
            presenter.clickDisableAuth();
        }
    }

    @UiHandler("authorizeButton")
    void authorize(ClickEvent event) {
        presenter.clickExecuteAuth();
    }

    @UiHandler("cancelAuthButton")
    void cancelAuth(ClickEvent event) {
        presenter.clickCancelAuth();
    }

    @UiHandler("discloseScopeInfo")
    void discloseScopeInfo(ClickEvent event) {
        clickedToDiscloseScopeInfo = true;
        showScopeInfoPopup();
    }

    @UiHandler("discloseScopeInfo")
    void scopeInfoHover(MouseOverEvent event) {
        showScopeInfoPopup();
    }

    @UiHandler("discloseScopeInfo")
    void scopeInfoMouseOut(MouseOutEvent event) {
        if (!clickedToDiscloseScopeInfo) {
            scopeInfoPopup.hide();
        }
    }

    private void showScopeInfoPopup() {
        scopeInfoPopup.setPopupPositionAndShow(new PositionCallback() {
            @Override
            public void setPosition(int offsetWidth, int offsetHeight) {
                int left = discloseScopeInfo.getAbsoluteLeft() - offsetWidth + discloseScopeInfo.getOffsetWidth();
                int top = discloseScopeInfo.getAbsoluteTop() + discloseScopeInfo.getOffsetHeight();
                scopeInfoPopup.setPopupPosition(left, top);
            }
        });
    }

    @Override
    public void setState(State state, Set<String> requiredScopes, Set<String> heldScopes) {
        scopeSelector.setVisible(state != State.ONLY_PUBLIC);
        authToggle.setValue(state == State.PRIVATE);

        Set<String> missingScopes = Sets.difference(requiredScopes, heldScopes);

        Set<String> scopesToShow;
        String message;
        AuthIconState iconState;
        if (missingScopes.isEmpty()) {
            iconState = AuthIconState.INFO;
            scopesToShow = heldScopes;
            if (heldScopes.isEmpty()) {
                message = "No auth required.";
            } else {
                message = "Auth scopes held: ";
            }
        } else if (missingScopes.equals(requiredScopes)) {
            iconState = AuthIconState.ERROR;
            scopesToShow = Collections.emptySet();
            message = "Method requires authorized requests.";
        } else {
            iconState = AuthIconState.WARNING;
            scopesToShow = missingScopes;
            message = "Method may require additional auth scopes: ";
        }

        authInfoIcon.setVisible(iconState == AuthIconState.INFO);
        authWarningIcon.setVisible(iconState == AuthIconState.WARNING);
        authErrorIcon.setVisible(iconState == AuthIconState.ERROR);

        authMessage.setText(message);

        Iterable<String> shortNames = Iterables.transform(scopesToShow, new Function<String, String>() {
            @Override
            public String apply(String scopeUrl) {
                return AuthPresenter.scopeName(scopeUrl);
            }
        });
        String delimitedNames = Joiner.on(", ").join(shortNames);
        scopeList.setText(delimitedNames);
    }

    @Override
    public void setScopes(Map<String, AuthScope> scopes) {
        selectedScopes.clear();
        scopesFromDiscovery = scopes;
    }

    /**
     * Rebuild the popup from scratch with all of the scopes that we have from the last time we were
     * presented.
     */
    private void buildScopePopup() {

        scopePanel.clear();
        additionalScopePanel.clear();

        Set<TextBox> oldEditors = Sets.newLinkedHashSet(freeFormEditors);
        freeFormEditors.clear();

        // Hide the service scopes label if there aren't any.
        hasScopesText.setVisible(!scopesFromDiscovery.isEmpty());
        noScopesText.setVisible(scopesFromDiscovery.isEmpty());

        // Show different text on the free-form scopes section when there are discovery scopes.
        optionalAdditionalScopes.setVisible(!scopesFromDiscovery.isEmpty());

        for (final Map.Entry<String, AuthScope> scope : scopesFromDiscovery.entrySet()) {
            // Add the check box to the table.
            CheckBox scopeToggle = new CheckBox();

            SafeHtmlBuilder safeHtml = new SafeHtmlBuilder();
            safeHtml.appendEscaped(scope.getKey()).appendHtmlConstant("<br><span>")
                    .appendEscaped(scope.getValue().getDescription()).appendHtmlConstant("</span>");
            scopeToggle.setHTML(safeHtml.toSafeHtml());

            scopeToggle.addStyleName(style.discoveryScopeSelector());
            scopePanel.add(scopeToggle);

            // When the box is checked, add our scope to the selected list.
            scopeToggle.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    CheckBox checkBox = (CheckBox) event.getSource();
                    if (checkBox.getValue()) {
                        selectedScopes.add(scope.getKey());
                    } else {
                        selectedScopes.remove(scope.getKey());
                    }
                }
            });

            // Enable the check box if the scope is selected.
            scopeToggle.setValue(selectedScopes.contains(scope.getKey()));
        }

        // Process any scopes that are extra.
        for (TextBox editor : oldEditors) {
            if (!editor.getValue().trim().isEmpty()) {
                addFreeFormEditorRow(editor.getValue(), true);
            }
        }

        // There should always be one empty editor.
        addFreeFormEditorRow("", false);
    }

    /**
     * Add an editor row in the form of a textbox, that will allow an arbitrary scope to be added.
     */
    private FocusWidget addFreeFormEditorRow(String name, boolean showRemoveLink) {
        final FlowPanel newRow = new FlowPanel();

        // Create the new editor and do the appropriate bookkeeping.
        final TextBox scopeText = new TextBox();
        scopeText.setValue(name);
        newRow.add(scopeText);
        freeFormEditors.add(scopeText);

        final Label removeLink = new InlineLabel("X");
        removeLink.addStyleName(style.clickable());
        removeLink.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                freeFormEditors.remove(scopeText);
                additionalScopePanel.remove(newRow);

                if (freeFormEditors.isEmpty()) {
                    addFreeFormEditorRow("", false);
                }
            }
        });
        newRow.add(removeLink);
        removeLink.setVisible(showRemoveLink);

        // Add a handler to add a new editor when there is text in the existing editor.
        scopeText.addKeyDownHandler(new KeyDownHandler() {
            @Override
            public void onKeyDown(KeyDownEvent event) {
                TextBox editor = (TextBox) event.getSource();
                boolean isLastEditor = editor.equals(Iterables.getLast(freeFormEditors));
                if (isLastEditor && !editor.getValue().isEmpty()) {
                    presenter.addNewScope();
                    removeLink.setVisible(true);
                }
            }
        });

        additionalScopePanel.add(newRow);

        return scopeText;
    }

    @Override
    public Set<String> getSelectedScopes() {
        // Concatenate the structured scopes with the free-form scopes.
        Set<String> allScopes = Sets.newHashSet(selectedScopes);

        for (TextBox editor : freeFormEditors) {
            if (!editor.getValue().trim().isEmpty()) {
                allScopes.add(editor.getValue());
            }
        }

        return allScopes;
    }

    @Override
    public void showScopeDialog() {
        buildScopePopup();
        scopePopup.show();
        scopePopup.center();
    }

    @Override
    public void addScopeEditor() {
        addFreeFormEditorRow("", false);
    }

    @Override
    public void hideScopeDialog() {
        scopePopup.hide();
    }

    @Override
    public void preSelectScopes(Set<String> scopes) {
        selectedScopes.removeAll(scopesFromDiscovery.keySet());
        selectedScopes.addAll(scopes);
    }

    /** Possible states of the auth icon indicator. */
    private enum AuthIconState {
        WARNING, ERROR, INFO,
    }
}