com.google.walkaround.wave.server.servlet.UndercurrentHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.google.walkaround.wave.server.servlet.UndercurrentHandler.java

Source

/*
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * 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.walkaround.wave.server.servlet;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.gxp.base.GxpContext;
import com.google.gxp.html.HtmlClosure;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.walkaround.proto.ClientVars.ErrorVars;
import com.google.walkaround.proto.ClientVars.LiveClientVars;
import com.google.walkaround.proto.ClientVars.StaticClientVars;
import com.google.walkaround.proto.gson.ClientVarsGsonImpl;
import com.google.walkaround.proto.gson.ClientVarsGsonImpl.ErrorVarsGsonImpl;
import com.google.walkaround.proto.gson.ClientVarsGsonImpl.LiveClientVarsGsonImpl;
import com.google.walkaround.proto.gson.ClientVarsGsonImpl.StaticClientVarsGsonImpl;
import com.google.walkaround.proto.gson.ClientVarsGsonImpl.UdwLoadDataGsonImpl;
import com.google.walkaround.slob.server.AccessDeniedException;
import com.google.walkaround.slob.server.GsonProto;
import com.google.walkaround.slob.server.SlobFacilities;
import com.google.walkaround.slob.server.SlobNotFoundException;
import com.google.walkaround.slob.shared.ClientId;
import com.google.walkaround.slob.shared.SlobId;
import com.google.walkaround.util.server.Util;
import com.google.walkaround.util.server.servlet.AbstractHandler;
import com.google.walkaround.util.shared.RandomBase64Generator;
import com.google.walkaround.wave.server.Flag;
import com.google.walkaround.wave.server.FlagName;
import com.google.walkaround.wave.server.ObjectSession;
import com.google.walkaround.wave.server.ObjectSessionHelper;
import com.google.walkaround.wave.server.WaveLoader;
import com.google.walkaround.wave.server.WaveLoader.LoadedWave;
import com.google.walkaround.wave.server.auth.UserContext;
import com.google.walkaround.wave.server.conv.ConvStore;
import com.google.walkaround.wave.server.gxp.Wave;
import com.google.walkaround.wave.server.udw.UdwStore;

import org.json.JSONException;
import org.json.JSONObject;
import org.waveprotocol.wave.model.wave.ParticipantId;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Collections;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Serves the undercurrent client.
 *
 * @author ohler@google.com (Christian Ohler)
 */
public class UndercurrentHandler extends AbstractHandler {
    private static final String nocacheJs = Util
            .slurpRequired("com.google.walkaround.wave.client.Walkaround.nocache.js");

    @SuppressWarnings("unused")
    private static final Logger log = Logger.getLogger(UndercurrentHandler.class.getName());

    @Inject
    @Flag(FlagName.CLIENT_VERSION)
    int clientVersion;
    @Inject
    WaveLoader loader;
    @Inject
    Random random;
    @Inject
    RandomBase64Generator random64;
    @Inject
    ParticipantId participantId;
    @Inject
    @Named("channel api url")
    String channelApiUrl;
    @Inject
    ObjectSessionHelper sessionHelper;
    @Inject
    @Flag(FlagName.ANALYTICS_ACCOUNT)
    String analyticsAccount;
    @Inject
    UserContext userContext;
    @Inject
    @UdwStore
    SlobFacilities udwStore;
    @Inject
    @ConvStore
    SlobFacilities convStore;

    private void setResponseHeaders(HttpServletResponse resp) {
        // TODO(ohler): Figure out how to make static versions cacheable.  The
        // problem is that we inline GWT's nocache JS, and having that cached is
        // very confusing when debugging, and could lead to situations where a
        // browser caches the nocache JS but not the cacheable JS and expects the
        // server to still have the cacheable JS -- this won't be true if a new
        // client binary has been deployed to the server.

        // TODO(ohler): Find a systematic way of setting no-cache headers in all
        // handlers that need them.

        // http://www.mnot.net/cache_docs/#PRAGMA recommends not to use this;
        // TODO(ohler): Figure out if it does any good.  Walkaround doesn't work in
        // old browsers anyway, so we shouldn't need hacks.
        resp.setHeader("Pragma", "No-cache");
        // NOTE(ohler): Using this rather than just "no-cache" seems to fix the
        // client crash on back/forward due to re-use of client id.
        resp.setHeader("Cache-Control", "no-store, must-revalidate");

        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
    }

    private JSONObject clientVarString(@Nullable LiveClientVars liveClientVars,
            @Nullable StaticClientVars staticClientVars, @Nullable ErrorVars errorVars) {
        Preconditions
                .checkArgument(
                        Collections.frequency(ImmutableList.of(liveClientVars != null, staticClientVars != null,
                                errorVars != null), true) == 1,
                        "%s/%s/%s", liveClientVars, staticClientVars, errorVars);
        ClientVarsGsonImpl clientVars = new ClientVarsGsonImpl();
        if (liveClientVars != null) {
            clientVars.setLiveClientVars(liveClientVars);
        } else if (staticClientVars != null) {
            clientVars.setStaticClientVars(staticClientVars);
        } else {
            clientVars.setErrorVars(errorVars);
        }
        String jsonString = GsonProto.toJson(clientVars);
        try {
            // TODO(ohler): Find a way to embed a GSON-generated JSON literal in GXP
            // without serializing and re-parsing to org.json.JSONObject.  Perhaps
            // through JavascriptClosure?
            return new JSONObject(jsonString);
        } catch (JSONException e) {
            throw new RuntimeException("org.json failed to parse GSON's output: " + jsonString, e);
        }
    }

    private void writeErrorResponse(HttpServletRequest req, HttpServletResponse resp, String errorMessage)
            throws IOException {
        ErrorVarsGsonImpl errorVars = new ErrorVarsGsonImpl();
        errorVars.setErrorMessage(errorMessage);
        setResponseHeaders(resp);
        Wave.write(resp.getWriter(), new GxpContext(getLocale(req)), analyticsAccount,
                clientVarString(null, null, errorVars), true, inlineNocacheJs(), channelApiUrl);
    }

    private void writeLiveClientResponse(HttpServletRequest req, HttpServletResponse resp, ClientId clientId,
            LoadedWave wave) throws IOException {
        LiveClientVarsGsonImpl vars = new LiveClientVarsGsonImpl();
        vars.setClientVersion(clientVersion);
        vars.setRandomSeed(random.nextInt());
        vars.setUserEmail(participantId.getAddress());
        vars.setHaveOauthToken(userContext.hasOAuthCredentials());
        vars.setConvSnapshot(wave.getConvSnapshotWithDiffs());
        vars.setConvConnectResponse(sessionHelper.createConnectResponse(
                new ObjectSession(wave.getConvObjectId(), clientId, convStore.getRootEntityKind()),
                wave.getConvConnectResult()));
        if (wave.getUdw() != null) {
            UdwLoadDataGsonImpl udwLoadData = new UdwLoadDataGsonImpl();
            udwLoadData.setConnectResponse(sessionHelper.createConnectResponse(
                    new ObjectSession(wave.getUdw().getObjectId(), clientId, udwStore.getRootEntityKind()),
                    wave.getUdw().getConnectResult()));
            udwLoadData.setSnapshot(wave.getUdw().getSnapshot());
            vars.setUdw(udwLoadData);
        }
        setResponseHeaders(resp);
        Wave.write(resp.getWriter(), new GxpContext(getLocale(req)), analyticsAccount,
                clientVarString(vars, null, null), true, inlineNocacheJs(), channelApiUrl);
    }

    private void writeStaticClientResponse(HttpServletRequest req, HttpServletResponse resp, LoadedWave wave)
            throws IOException {
        StaticClientVarsGsonImpl vars = new StaticClientVarsGsonImpl();
        vars.setRandomSeed(random.nextInt());
        vars.setUserEmail(participantId.getAddress());
        vars.setHaveOauthToken(userContext.hasOAuthCredentials());
        vars.setConvObjectId(wave.getConvObjectId().getId());
        vars.setConvSnapshot(wave.getConvSnapshotWithDiffs());
        setResponseHeaders(resp);
        Wave.write(resp.getWriter(), new GxpContext(getLocale(req)), analyticsAccount,
                clientVarString(null, vars, null), true, inlineNocacheJs(), channelApiUrl);
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        SlobId convObjectId = new SlobId(requireParameter(req, "id"));
        String versionString = optionalParameter(req, "version", null);
        @Nullable
        Long version = versionString == null ? null : Long.parseLong(versionString);
        @Nullable
        ClientId clientId = version != null ? null
                : new ClientId(random64.next(
                        // TODO(ohler): justify this number
                        8));
        LoadedWave wave;
        try {
            if (version == null) {
                wave = loader.load(convObjectId, clientId);
            } else {
                wave = loader.loadStaticAtVersion(convObjectId, version);
            }
        } catch (AccessDeniedException e) {
            log.log(Level.SEVERE, "Wave not found or access denied", e);
            writeErrorResponse(req, resp, "Wave not found or access denied");
            return;
        } catch (SlobNotFoundException e) {
            log.log(Level.SEVERE, "Wave not found or access denied", e);
            writeErrorResponse(req, resp, "Wave not found or access denied");
            return;
        } catch (IOException e) {
            log.log(Level.SEVERE, "Server error loading wave", e);
            writeErrorResponse(req, resp, "Server error loading wave");
            return;
        }
        if (version == null) {
            writeLiveClientResponse(req, resp, clientId, wave);
        } else {
            writeStaticClientResponse(req, resp, wave);
        }
    }

    private HtmlClosure inlineNocacheJs() {
        return new HtmlClosure() {
            @Override
            public void write(Appendable out, GxpContext gxpContext) throws IOException {
                out.append("<script type='text/javascript'>\n");
                // No need to escape this, GWT's nocache javascript is already escaped for
                // the purpose of direct inclusion into script elements.
                out.append(nocacheJs);
                out.append("</script>");
            }
        };
    }
}