name.martingeisse.webide.util.NewWindowInterceptBehavior.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.webide.util.NewWindowInterceptBehavior.java

Source

/**
 * Copyright (c) 2010 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.webide.util;

import java.util.UUID;

import org.apache.wicket.Component;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxNewWindowNotifyingBehavior;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.behavior.IBehaviorListener;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.PriorityHeaderItem;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.util.string.StringValue;

/**
 * This behavior serves a similar purpose as {@link AjaxNewWindowNotifyingBehavior}.
 * It also detects when a page is loaded into a new browser window/tab.
 * Unlike AjaxNewWindowNotifyingBehavior, however, it doesn't send a delayed AJAX
 * callback in that case, but intercepts client-side scripts right at the top
 * (using a {@link PriorityHeaderItem}) such that no further scripts are executed,
 * and redirects the browser to the callback URL of this behavior. A typical
 * subclass of this class would then issue a redirect (e.g. to a newly created
 * page).
 * 
 * The purpose of this class is to avoid callbacks happening from the second tab
 * that could interfere with the rendered page in the first tab, both "normal"
 * AJAX callbacks that happen right on-ready as well as establishing an
 * Atmosphere connection.
 * 
 * For example, a stateful page with an Atmosphere resource would not be very
 * user-friendly when rendered in multiple tabs (since the page's UI in the
 * different tabs would affect each other), and per-tab state (such as AJAX
 * delta-rendered components), would not work at all without some really dirty
 * tricks; performance would not be great either since we cannot know whether
 * "old" Atmosphere resources are still used, so all push-renderings must happen
 * for all old resources, even though most of them are not used anymore. Instead,
 * this behavior allows to send all new tabs to new pages, so each page is visible
 * only in a single tab and all old Atmosphere resources can be discarded (the
 * latter is NOT done by this behavior, though).
 */
public abstract class NewWindowInterceptBehavior extends Behavior implements IBehaviorListener {

    /**
     * A unique name used for the page window's name
     */
    private final String windowName;

    /**
     * The name of the HTTP request parameter that brings the current page window's name.
     */
    private static final String PARAM_WINDOW_NAME = "windowName";

    /**
     * A flag whether this behavior has been rendered at least once.
     */
    private boolean hasBeenRendered;

    /**
     * Constructor.
     */
    public NewWindowInterceptBehavior() {
        this(UUID.randomUUID().toString());
    }

    /**
     * Constructor.
     *
     * @param windowName the custom name to use for the page's window
     */
    public NewWindowInterceptBehavior(final String windowName) {
        this.windowName = windowName;
    }

    /* (non-Javadoc)
     * @see org.apache.wicket.behavior.Behavior#bind(org.apache.wicket.Component)
     */
    @Override
    public void bind(Component component) {
        super.bind(component);
        if (component instanceof WebPage == false) {
            throw new WicketRuntimeException(
                    AjaxNewWindowNotifyingBehavior.class.getName() + " can be assigned only to WebPage instances.");
        }
    }

    /**
     * Returns the callback URL for the specified component.
     * @param component the component
     * @return the URL
     */
    public CharSequence getUrl(Component component) {
        return component.urlFor(this, IBehaviorListener.INTERFACE, null);
    }

    @Override
    public void renderHead(final Component component, final IHeaderResponse response) {
        super.renderHead(component, response);
        StringBuilder builder = new StringBuilder();

        // set the window name on first rendering; this affects only the first tab
        if (!hasBeenRendered) {
            hasBeenRendered = true;
            builder.append("window.name='").append(windowName).append("';\n");
        }

        // check the name each time; in the first tab this won't do anything
        {
            builder.append("if (window.name != '").append(windowName).append("') {\n");
            builder.append("\tlocation.href = '").append(getUrl(component)).append("';\n");
            builder.append("}\n");
        }

        response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forScript(builder.toString(), null)));
    }

    /* (non-Javadoc)
     * @see org.apache.wicket.behavior.IBehaviorListener#onRequest()
     */
    @Override
    public void onRequest() {
        final RequestCycle requestCycle = RequestCycle.get();
        final StringValue uuidParam = requestCycle.getRequest().getRequestParameters()
                .getParameterValue(PARAM_WINDOW_NAME);
        if (!windowName.equals(uuidParam.toString())) {
            IRequestHandler scheduledHandler1 = requestCycle.getRequestHandlerScheduledAfterCurrent();
            onNewWindow();
            IRequestHandler scheduledHandler2 = requestCycle.getRequestHandlerScheduledAfterCurrent();
            if (scheduledHandler1 == scheduledHandler2) {
                throw new RuntimeException(
                        "NewWindowInterceptBehavior.onNewWindow() did not schedule a new request handler -- "
                                + "this would cause an infinite loop, so we break here");
            }
        }
    }

    /**
     * A callback method when a new window/tab is opened for a page instance
     * which is already opened in another window/tab. This method should issue
     * a redirect, otherwise the current page will be reloaded in an
     * infinite loop.
     */
    protected abstract void onNewWindow();

}