/** * Copyright 2012 A. Kaplanov * * 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 org.waveprotocol.box.server.waveserver; import com.google.gxp.org.apache.xerces.impl.dv.util.Base64; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.protobuf.ByteString; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.io.StringWriter; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.logging.Level; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.json.JSONArray; import org.json.JSONObject; import org.waveprotocol.box.server.persistence.AttachmentStore; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.InvalidParticipantAddress; import org.waveprotocol.wave.util.logging.Log; import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta; import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletOperation; import org.waveprotocol.wave.model.id.IdURIEncoderDecoder; 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.version.HashedVersionFactory; import org.waveprotocol.wave.model.version.HashedVersionFactoryImpl; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.util.escapers.jvm.JavaUrlCodec; /** * * @author (akaplanov@gmail.com) (Andrew Kaplanov) */ @SuppressWarnings("serial") @Singleton public class ImportServlet extends HttpServlet { private static final Log LOG = Log.get(ImportServlet.class); public static final String GWAVE_PUBLIC_DOMAIN = "a.gwave.com"; public static final String GWAVE_PUBLIC_USER_NAME = "public"; public static final String WIAB_SHARED_USER_NAME = ""; private static final IdURIEncoderDecoder URI_CODEC = new IdURIEncoderDecoder(new JavaUrlCodec()); private static final HashedVersionFactory HASH_FACTORY = new HashedVersionFactoryImpl(URI_CODEC); private final WaveletProvider waveletProvider; private final AttachmentStore attachmentStore; private final WaveMap waveMap; @Inject private ImportServlet(WaveletProvider waveletProvider, AttachmentStore attachmentStore, WaveMap waveMap) { this.waveletProvider = waveletProvider; this.attachmentStore = attachmentStore; this.waveMap = waveMap; } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String domain = request.getHeader("domain"); WaveId waveId = WaveId.deserialise(request.getHeader("waveId")); WaveletId waveletId = WaveletId.deserialise(request.getHeader("waveletId")); final StringWriter error = new StringWriter(); boolean somethingProcessed = false; boolean somethingSkipped = false; try { JSONObject exp = new JSONObject(readToString(request.getReader())); JSONArray rawDeltas = exp.getJSONObject("data").getJSONArray("rawDeltas"); if (rawDeltas.length() != 0) { List<ProtocolAppliedWaveletDelta> deltas = new LinkedList<ProtocolAppliedWaveletDelta>(); for (int i = 0; i < rawDeltas.length(); i++) { deltas.add(ProtocolAppliedWaveletDelta.parseFrom(Base64.decode(rawDeltas.getString(i)))); } WaveletName waveletName = WaveletName.of(waveId, waveletId); LocalWaveletContainerImpl wavelet = (LocalWaveletContainerImpl) waveMap.getLocalWavelet(waveletName); Set<ParticipantId> participants = new HashSet<ParticipantId>(); if (wavelet != null) { participants.addAll(wavelet.accessSnapshot().getParticipants()); } for (ProtocolAppliedWaveletDelta appliedDelta : deltas) { ProtocolWaveletDelta delta = ProtocolWaveletDelta.parseFrom( appliedDelta.getSignedOriginalDelta().getDelta()); long currentVersion = 0; if (wavelet != null) { currentVersion = wavelet.getCurrentVersion().getVersion(); } if (currentVersion == delta.getHashedVersion().getVersion()) { ProtocolWaveletDelta newDelta = convertDelta(delta, domain, wavelet, waveletName, participants); waveletProvider.submitRequest(waveletName, newDelta, new WaveletProvider.SubmitRequestListener() { @Override public void onSuccess(int operationsApplied, HashedVersion hashedVersionAfterApplication, long applicationTimestamp) { } @Override public void onFailure(String errorMessage) { error.write(errorMessage); } }); if (error.getBuffer().length() != 0) { break; } if (wavelet == null) { wavelet = (LocalWaveletContainerImpl) waveMap.getLocalWavelet(waveletName); } somethingProcessed = true; } else { somethingSkipped = true; } } } } catch (Exception ex) { LOG.log(Level.SEVERE, "waveId " + waveId.toString() + ", waveletId " + waveletId.toString(), ex); throw new IOException(ex); } response.setStatus(HttpServletResponse.SC_OK); if (error.getBuffer().length() != 0) { response.getOutputStream().write(("error : " + error.getBuffer()).getBytes()); } else if (somethingProcessed && !somethingSkipped) { response.getOutputStream().write(("imported").getBytes()); } else if (somethingProcessed && somethingSkipped) { response.getOutputStream().write(("appended").getBytes()); } else { response.getOutputStream().write(("skipped").getBytes()); } } /** * Convert delta from GWave to Wiab * * @param delta from GWave * @param domain target domain * @param wavelet to append delta * @param waveletName name of wavelet * @param set participants of wavelet at this moment * @return delta to import * @throws InvalidParticipantAddress deserialize of participant error */ protected static ProtocolWaveletDelta convertDelta(ProtocolWaveletDelta delta, String domain, LocalWaveletContainerImpl wavelet, WaveletName waveletName, Set<ParticipantId> participants) throws InvalidParticipantAddress { ProtocolWaveletDelta.Builder newDelta = ProtocolWaveletDelta.newBuilder(delta); ParticipantId creator = null; if (wavelet != null) { creator = wavelet.getCreator(); } ParticipantId author = makeParticipantId(delta.getAuthor(), domain); if (!participants.contains(author) && creator != null) { // Assign the authorship of the delta from internal GWave users // (panda@a.gwave.com, spelly@a.gwave.com) or others to the creator of wave. if (!author.getAddress().endsWith("@" + GWAVE_PUBLIC_DOMAIN)) { LOG.warning("Unknown participant " + author.getAddress() + ", wave " + wavelet.getWaveletName().waveId.getId()); } author = creator; } newDelta.setAuthor(author.getAddress()); for (int i = 0; i < delta.getOperationCount(); i++) { ProtocolWaveletOperation op = delta.getOperation(i); ProtocolWaveletOperation.Builder newOp = ProtocolWaveletOperation.newBuilder(op); if (op.hasAddParticipant()) { initAddParticipantOperation(newOp, op, domain, participants); if (creator == null && newOp.hasAddParticipant()) { creator = ParticipantId.of(newOp.getAddParticipant()); } } else if (op.hasRemoveParticipant()) { initRemoveParticipantOperation(newOp, op, domain, participants); } // TODO (Andrew Kaplanov) import attachments newDelta.setOperation(i, newOp); } if (wavelet == null) { ProtocolHashedVersion ver = ProtocolHashedVersion.newBuilder(delta.getHashedVersion()). setHistoryHash(ByteString.copyFrom(HASH_FACTORY.createVersionZero(waveletName).getHistoryHash())). build(); newDelta.setHashedVersion(ver); } else { ProtocolHashedVersion ver = ProtocolHashedVersion.newBuilder(delta.getHashedVersion()). setHistoryHash(ByteString.copyFrom(wavelet.getCurrentVersion().getHistoryHash())). build(); newDelta.setHashedVersion(ver); } return newDelta.build(); } /** * Convert adding participant operation. * Skip operation if participant already exists. */ private static void initAddParticipantOperation(ProtocolWaveletOperation.Builder newOperation, ProtocolWaveletOperation operation, String domain, Set<ParticipantId> participants) throws InvalidParticipantAddress { ParticipantId participant = makeParticipantId(operation.getAddParticipant(), domain); if (!participants.contains(participant)) { newOperation.setAddParticipant(participant.getAddress()); participants.add(participant); } else { newOperation.setNoOp(true); } } /** * Convert removal participant operation. * Skip operation if nothing to remove. */ private static void initRemoveParticipantOperation(ProtocolWaveletOperation.Builder newOperation, ProtocolWaveletOperation operation, String domain, Set<ParticipantId> participants) throws InvalidParticipantAddress { ParticipantId participant = makeParticipantId(operation.getRemoveParticipant(), domain); if (participants.contains(participant)) { newOperation.setRemoveParticipant(participant.getAddress()); participants.remove(participant); } else { newOperation.setNoOp(true); } } /** * Make WIAB participant Id * * @param participant in GWave * @param domain of WIAB server */ private static ParticipantId makeParticipantId(String participant, String domain) throws InvalidParticipantAddress { int index = participant.indexOf('@'); if (index != -1) { if (participant.substring(0, index).equals(GWAVE_PUBLIC_USER_NAME) && participant.substring(index + 1).equals(GWAVE_PUBLIC_DOMAIN)) { participant = WIAB_SHARED_USER_NAME + "@" + domain; } else { participant = participant.substring(0, index + 1) + domain; } } return ParticipantId.of(participant); } private static String readToString(Reader reader) throws FileNotFoundException, IOException { StringBuilder sb = new StringBuilder(); char buf[] = new char[1000]; for (;;) { int ret = reader.read(buf, 0, buf.length); if (ret == -1) { break; } sb.append(buf, 0, ret); } return sb.toString(); } }