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.googleimport; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.net.UriEscapers; import com.google.gxp.base.GxpContext; import com.google.gxp.html.HtmlClosure; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.walkaround.proto.ImportSettings; import com.google.walkaround.proto.ImportSettings.ImportSharingMode; import com.google.walkaround.proto.ImportTaskPayload; import com.google.walkaround.proto.ImportWaveletTask; import com.google.walkaround.proto.gson.ImportSettingsGsonImpl; import com.google.walkaround.proto.gson.ImportTaskPayloadGsonImpl; import com.google.walkaround.proto.gson.ImportWaveletTaskGsonImpl; import com.google.walkaround.slob.shared.SlobId; import com.google.walkaround.util.server.HtmlEscaper; import com.google.walkaround.util.server.RetryHelper; import com.google.walkaround.util.server.RetryHelper.PermanentFailure; import com.google.walkaround.util.server.RetryHelper.RetryableFailure; import com.google.walkaround.util.server.appengine.CheckedDatastore; import com.google.walkaround.util.server.appengine.CheckedDatastore.CheckedTransaction; import com.google.walkaround.util.server.auth.InvalidSecurityTokenException; import com.google.walkaround.util.server.servlet.AbstractHandler; import com.google.walkaround.util.server.servlet.BadRequestException; import com.google.walkaround.util.shared.Assert; import com.google.walkaround.wave.server.Flag; import com.google.walkaround.wave.server.FlagName; import com.google.walkaround.wave.server.auth.NeedNewOAuthTokenException; import com.google.walkaround.wave.server.auth.StableUserId; import com.google.walkaround.wave.server.auth.UserContext; import com.google.walkaround.wave.server.auth.XsrfHelper; import com.google.walkaround.wave.server.auth.XsrfHelper.XsrfTokenExpiredException; import com.google.walkaround.wave.server.gxp.ImportOverviewFragment; import com.google.walkaround.wave.server.gxp.ImportWaveletDisplayRecord; import com.google.walkaround.wave.server.gxp.SourceInstance; import com.google.walkaround.wave.server.servlet.PageSkinWriter; import org.joda.time.Instant; import org.joda.time.LocalDate; import org.waveprotocol.wave.model.id.WaveId; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.id.WaveletName; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.model.wave.ParticipantId; import java.io.IOException; import java.util.List; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Interactive entry point for import features. * * @author ohler@google.com (Christian Ohler) */ // For now, this shows a lot of detail, more than typical users would want. // Once it works well, we should simplify it. public class ImportOverviewHandler extends AbstractHandler { @SuppressWarnings("unused") private static final Logger log = Logger.getLogger(ImportOverviewHandler.class.getName()); private static final String XSRF_ACTION = "importaction"; @Inject ParticipantId participantId; @Inject StableUserId userId; @Inject XsrfHelper xsrfHelper; @Inject SourceInstance.Factory sourceInstanceFactory; @Inject TaskDispatcher taskDispatcher; // Providers because the values are only needed in some branches of the code. @Inject Provider<CheckedDatastore> datastore; @Inject Provider<PerUserTable> perUserTable; @Inject Provider<FindRemoteWavesProcessor> findProcessor; @Inject PageSkinWriter pageSkinWriter; @Inject UserContext userContext; @Inject @Flag(FlagName.IMPORT_PRESERVE_HISTORY) boolean preserveHistory; private String makeLocalWaveLink(SlobId convSlobId) { return "/wave?id=" + UriEscapers.uriQueryStringEscaper(false).escape(convSlobId.getId()); } private List<ImportWaveletDisplayRecord> getWaves(CheckedTransaction tx, Multimap<Pair<SourceInstance, WaveletName>, ImportSharingMode> importsInProgress) throws RetryableFailure, PermanentFailure { ImmutableList.Builder<ImportWaveletDisplayRecord> out = ImmutableList.builder(); List<RemoteConvWavelet> wavelets = perUserTable.get().getAllWavelets(tx, userId); for (RemoteConvWavelet wavelet : wavelets) { WaveletName waveletName = WaveletName.of(WaveId.deserialise(wavelet.getDigest().getWaveId()), wavelet.getWaveletId()); out.add(new ImportWaveletDisplayRecord(wavelet.getSourceInstance(), waveletName, wavelet.getSourceInstance().getWaveLink(waveletName.waveId), // Let's assume that participant 0 is the creator even if that's not always true. // Participant lists can be empty. wavelet.getDigest().getParticipantSize() == 0 ? "<unknown>" : wavelet.getDigest().getParticipant(0), wavelet.getDigest().getTitle(), "" + new LocalDate(new Instant(wavelet.getDigest().getLastModifiedMillis())), importsInProgress.containsEntry(Pair.of(wavelet.getSourceInstance(), waveletName), ImportSharingMode.PRIVATE) || importsInProgress.containsEntry(Pair.of(wavelet.getSourceInstance(), waveletName), ImportSharingMode.PRIVATE_UNLESS_PARTICIPANT), wavelet.getPrivateLocalId() == null ? null : wavelet.getPrivateLocalId().getId(), wavelet.getPrivateLocalId() == null ? null : makeLocalWaveLink(wavelet.getPrivateLocalId()), importsInProgress.containsEntry(Pair.of(wavelet.getSourceInstance(), waveletName), ImportSharingMode.SHARED) || importsInProgress.containsEntry(Pair.of(wavelet.getSourceInstance(), waveletName), ImportSharingMode.PRIVATE_UNLESS_PARTICIPANT), wavelet.getSharedLocalId() == null ? null : wavelet.getSharedLocalId().getId(), wavelet.getSharedLocalId() == null ? null : makeLocalWaveLink(wavelet.getSharedLocalId()))); } return out.build(); } private String getInstanceSelectionHtml() { StringBuilder out = new StringBuilder(); boolean first = true; for (SourceInstance instance : sourceInstanceFactory.getInstances()) { String instanceId = instance.serialize(); Assert.check(instanceId.matches("[a-zA-Z_.]+"), "Bad characters in instance id: %s", instance); out.append("<input type='radio'" + (first ? " checked='checked'" : "") + " name='instance' value='" + instanceId + "'/> " + HtmlEscaper.HTML_ESCAPER.escape(instance.getLongName()) + "<br/>"); first = false; } return out.toString(); } private List<String> describeTasks(List<ImportTask> tasks) { ImmutableList.Builder<String> out = ImmutableList.builder(); for (ImportTask task : tasks) { out.add(taskDispatcher.describeTask(task)); } return out.build(); } @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (!userContext.hasOAuthCredentials()) { throw new NeedNewOAuthTokenException("No OAuth credentials: " + userContext); } Pair<List<String>, List<ImportWaveletDisplayRecord>> pair; try { pair = new RetryHelper() .run(new RetryHelper.Body<Pair<List<String>, List<ImportWaveletDisplayRecord>>>() { @Override public Pair<List<String>, List<ImportWaveletDisplayRecord>> run() throws RetryableFailure, PermanentFailure { CheckedTransaction tx = datastore.get().beginTransaction(); try { List<ImportTask> tasksInProgress = perUserTable.get().getAllTasks(tx, userId); return Pair.of(describeTasks(tasksInProgress), getWaves(tx, taskDispatcher.waveletImportsInProgress(tasksInProgress))); } finally { tx.rollback(); } } }); } catch (PermanentFailure e) { throw new IOException("PermanentFailure retrieving import records", e); } List<String> tasksInProgress = pair.getFirst(); List<ImportWaveletDisplayRecord> waveDisplayRecords = pair.getSecond(); final String instanceSelectionHtml = getInstanceSelectionHtml(); resp.setContentType("text/html"); resp.setCharacterEncoding("UTF-8"); pageSkinWriter.write("Walkaround Import", participantId.getAddress(), ImportOverviewFragment .getGxpClosure(participantId.getAddress(), xsrfHelper.createToken(XSRF_ACTION), new HtmlClosure() { @Override public void write(Appendable out, GxpContext context) throws IOException { out.append(instanceSelectionHtml); } }, tasksInProgress, waveDisplayRecords)); } @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (!userContext.hasOAuthCredentials()) { throw new NeedNewOAuthTokenException("POST with no OAuth credentials: " + userContext); } try { xsrfHelper.verify(XSRF_ACTION, requireParameter(req, "token")); } catch (XsrfTokenExpiredException e) { throw new BadRequestException(e); } catch (InvalidSecurityTokenException e) { throw new BadRequestException(e); } String action = requireParameter(req, "action"); if ("findwaves".equals(action) || "findandimport".equals(action)) { SourceInstance instance = sourceInstanceFactory.parseUnchecked(requireParameter(req, "instance")); // Rather than enqueueing just one interval 2008-01-01 to 2013-01-01, we // split that interval into random parts. See the note on randomization // in FindRemoteWavesProcessor. log.info("Enqueueing find waves tasks"); @Nullable ImportSettings autoImportSettings; if ("findwaves".equals(action)) { autoImportSettings = null; } else { autoImportSettings = new ImportSettingsGsonImpl(); autoImportSettings.setSynthesizeHistory(!preserveHistory); if ("private".equals(requireParameter(req, "sharingmode"))) { autoImportSettings.setSharingMode(ImportSharingMode.PRIVATE); } else if ("shared".equals(requireParameter(req, "sharingmode"))) { autoImportSettings.setSharingMode(ImportSharingMode.SHARED); } else if ("privateunlessparticipant".equals(requireParameter(req, "sharingmode"))) { autoImportSettings.setSharingMode(ImportSharingMode.PRIVATE_UNLESS_PARTICIPANT); } else { throw new BadRequestException("Bad sharingmode"); } } enqueueTasks(findProcessor.get().makeRandomTasksForInterval(instance, DaysSinceEpoch.fromYMD(2008, 1, 1), DaysSinceEpoch.fromYMD(2013, 1, 1), autoImportSettings)); } else if ("importwavelet".equals(action)) { SourceInstance instance = sourceInstanceFactory.parseUnchecked(requireParameter(req, "instance")); WaveId waveId = WaveId.deserialise(requireParameter(req, "waveid")); WaveletId waveletId = WaveletId.deserialise(requireParameter(req, "waveletid")); ImportWaveletTask task = new ImportWaveletTaskGsonImpl(); task.setInstance(instance.serialize()); task.setWaveId(waveId.serialise()); task.setWaveletId(waveletId.serialise()); ImportSettings settings = new ImportSettingsGsonImpl(); if ("private".equals(requireParameter(req, "sharingmode"))) { settings.setSharingMode(ImportSettings.ImportSharingMode.PRIVATE); } else if ("shared".equals(requireParameter(req, "sharingmode"))) { settings.setSharingMode(ImportSettings.ImportSharingMode.SHARED); } else { throw new BadRequestException("Unexpected import sharing mode"); } settings.setSynthesizeHistory(!preserveHistory); task.setSettings(settings); @Nullable String existingSlobIdToIgnore = optionalParameter(req, "ignoreexisting", null); if (existingSlobIdToIgnore != null) { task.setExistingSlobIdToIgnore(existingSlobIdToIgnore); } final ImportTaskPayload payload = new ImportTaskPayloadGsonImpl(); payload.setImportWaveletTask(task); log.info("Enqueueing import task for " + waveId + "; synthesizeHistory=" + task.getSettings().getSynthesizeHistory()); enqueueTasks(ImmutableList.of(payload)); } else if ("canceltasks".equals(action)) { log.info("Cancelling all tasks for " + userId); try { new RetryHelper().run(new RetryHelper.VoidBody() { @Override public void run() throws RetryableFailure, PermanentFailure { CheckedTransaction tx = datastore.get().beginTransaction(); try { if (perUserTable.get().deleteAllTasks(tx, userId)) { tx.commit(); } } finally { tx.close(); } } }); } catch (PermanentFailure e) { throw new IOException("Failed to delete tasks", e); } } else if ("forgetwaves".equals(action)) { log.info("Forgetting all waves for " + userId); try { new RetryHelper().run(new RetryHelper.VoidBody() { @Override public void run() throws RetryableFailure, PermanentFailure { CheckedTransaction tx = datastore.get().beginTransaction(); try { if (perUserTable.get().deleteAllWaves(tx, userId)) { tx.commit(); } } finally { tx.close(); } } }); } catch (PermanentFailure e) { throw new IOException("Failed to delete tasks", e); } } else { throw new BadRequestException("Unknown action: " + action); } // TODO(ohler): Send 303, not 302. See // http://en.wikipedia.org/wiki/Post/Redirect/Get . resp.sendRedirect(req.getServletPath()); } private void enqueueTasks(final List<ImportTaskPayload> payloads) throws IOException { try { new RetryHelper().run(new RetryHelper.VoidBody() { @Override public void run() throws RetryableFailure, PermanentFailure { CheckedTransaction tx = datastore.get().beginTransaction(); try { for (ImportTaskPayload payload : payloads) { perUserTable.get().addTask(tx, userId, payload); } tx.commit(); } finally { tx.close(); } } }); } catch (PermanentFailure e) { throw new IOException("Failed to enqueue import task", e); } } }