Java tutorial
/* * 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>"); } }; } }