com.google.walkaround.wave.server.googleimport.ImportOverviewHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.google.walkaround.wave.server.googleimport.ImportOverviewHandler.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.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);
        }
    }

}