Package org.waveprotocol.box.server.robots

Source Code of org.waveprotocol.box.server.robots.OperationContextImpl

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.robots;

import static org.waveprotocol.box.server.robots.util.RobotsUtil.createEmptyRobotWavelet;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.wave.api.ApiIdSerializer;
import com.google.wave.api.InvalidRequestException;
import com.google.wave.api.JsonRpcConstant.ParamsProperty;
import com.google.wave.api.JsonRpcResponse;
import com.google.wave.api.OperationRequest;
import com.google.wave.api.data.converter.EventDataConverter;
import com.google.wave.api.event.Event;
import com.google.wave.api.event.EventSerializationException;
import com.google.wave.api.event.EventSerializer;
import com.google.wave.api.event.EventType;
import com.google.wave.api.event.OperationErrorEvent;

import org.waveprotocol.box.common.Receiver;
import org.waveprotocol.box.server.frontend.CommittedWaveletSnapshot;
import org.waveprotocol.box.server.robots.util.ConversationUtil;
import org.waveprotocol.box.server.robots.util.OperationUtil;
import org.waveprotocol.box.server.waveserver.WaveServerException;
import org.waveprotocol.box.server.waveserver.WaveletProvider;
import org.waveprotocol.wave.model.conversation.Conversation;
import org.waveprotocol.wave.model.conversation.ConversationBlip;
import org.waveprotocol.wave.model.conversation.ObservableConversationView;
import org.waveprotocol.wave.model.id.IdUtil;
import org.waveprotocol.wave.model.id.InvalidIdException;
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.operation.wave.TransformedWaveletDelta;
import org.waveprotocol.wave.model.schema.SchemaCollection;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument;
import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
import org.waveprotocol.wave.util.logging.Log;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Class which provides context for robot operations and gives access to the
* results.
*
* @author ljvderijk@google.com (Lennard de Rijk)
*/
public class OperationContextImpl implements OperationContext, OperationResults {

  private static final Log LOG = Log.get(OperationContextImpl.class);

  private static final ObservableWaveletData.Factory<? extends ObservableWaveletData> FACTORY =
      WaveletDataImpl.Factory.create(
          ObservablePluggableMutableDocument.createFactory(SchemaCollection.empty()));

  /**
   * Maps operation ID's to responses.
   */
  private final Map<String, JsonRpcResponse> responses = Maps.newHashMap();

  /**
   * {@link WaveletProvider} that gives us access to wavelets.
   */
  private final WaveletProvider waveletProvider;

  /**
   * {@link EventDataConverter} that can convert to
   * {@link com.google.wave.api.impl.WaveletData} and such.
   */
  private final EventDataConverter converter;

  /**
   * The wavelet to which this context is bound, null if unbound.
   */
  private final RobotWaveletData boundWavelet;

  /**
   * The wavelets that have been opened in the lifespan of this context.
   */
  private final Map<WaveletName, RobotWaveletData> openedWavelets = Maps.newHashMap();

  /** Stores temporary blip ids -> real blip ids */
  private final Map<String, String> tempBlipIdMap = Maps.newHashMap();
  /** Stores temporary wavelet names -> real wavelet names */
  private final Map<WaveletName, WaveletName> tempWaveletNameMap = Maps.newHashMap();
  /** Caches {@link ObservableConversationView}s */
  private final Map<WaveletName, Map<ParticipantId, ObservableConversationView>>
  openedConversations;

  /** Used to create conversations. */
  private final ConversationUtil conversationUtil;

  /**
   * Constructs an operation context not bound to any wavelet.
   *
   * @param waveletProvider the waveletprovider to use for querying wavelet.
   * @param converter {@link EventDataConverter} for converting from server side
   *        objects.
   * @param conversationUtil used to create conversations.
   */
  public OperationContextImpl(WaveletProvider waveletProvider, EventDataConverter converter,
      ConversationUtil conversationUtil) {
    this(waveletProvider, converter, conversationUtil, null);
  }

  /**
   * Constructs a bound operation context. The bound wavelet is added to the
   * list of open wavelets.
   *
   * @param waveletProvider the waveletprovider to use for querying wavelet.
   * @param converter {@link EventDataConverter} for converting from server side
   *        objects.
   * @param boundWavelet the wavelet to bind this context to, null for an
   *        unbound context.
   * @param conversationUtil used to create conversations.
   */
  public OperationContextImpl(WaveletProvider waveletProvider, EventDataConverter converter,
      ConversationUtil conversationUtil, RobotWaveletData boundWavelet) {
    this.waveletProvider = waveletProvider;
    this.converter = converter;
    this.conversationUtil = conversationUtil;
    this.boundWavelet = boundWavelet;
    this.openedConversations = Maps.newHashMap();

    if (boundWavelet != null) {
      openedWavelets.put(boundWavelet.getWaveletName(), boundWavelet);
    }
  }

  // OperationContext implementation begins here

  @Override
  public EventDataConverter getConverter() {
    return converter;
  }

  @Override
  public boolean isBound() {
    return boundWavelet != null;
  }

  @Override
  public Map<WaveletName, RobotWaveletData> getOpenWavelets() {
    return Collections.unmodifiableMap(openedWavelets);
  }

  @Override
  public void constructResponse(OperationRequest operation, Map<ParamsProperty, Object> data) {
    setResponse(operation.getId(), JsonRpcResponse.result(operation.getId(), data));
  }

  @Override
  public void constructErrorResponse(OperationRequest operation, String errorMessage) {
    setResponse(operation.getId(), JsonRpcResponse.error(operation.getId(), errorMessage));
  }

  @Override
  public void processEvent(OperationRequest operation, Event event) throws InvalidRequestException {
    // Create JSON-RPC error response.
    if (event.getType() == EventType.OPERATION_ERROR) {
      constructErrorResponse(operation, OperationErrorEvent.as(event).getMessage());
      return;
    }
    // Create JSON-RPC success response.
    try {
      constructResponse(operation, EventSerializer.extractPropertiesToParamsPropertyMap(event));
    } catch (EventSerializationException e) {
      LOG.severe("Internal Error occurred, when serializing events", e);
      throw new InvalidRequestException("Unable to serialize events", operation);
    }
  }

  @Override
  public void putWavelet(WaveId waveId, WaveletId waveletId, RobotWaveletData newWavelet) {
    WaveletName waveletName = newWavelet.getWaveletName();
    Preconditions.checkArgument(!openedWavelets.containsKey(waveletName),
        "Not allowed to put an already open wavelet in as a new wavelet");

    // New wavelets are indicated by the temporary marker in their waveId.
    if (waveId.getId().startsWith(TEMP_ID_MARKER)) {
      tempWaveletNameMap.put(WaveletName.of(waveId, waveletId), waveletName);
    }
    openedWavelets.put(waveletName, newWavelet);
  }

  @Override
  public OpBasedWavelet openWavelet(WaveId waveId, WaveletId waveletId, ParticipantId participant)
      throws InvalidRequestException {
    WaveletName waveletName;
    if (waveId.getId().startsWith(TEMP_ID_MARKER)) {
      WaveletName tempWaveletName = WaveletName.of(waveId, waveletId);
      waveletName = tempWaveletNameMap.get(tempWaveletName);
    } else {
      waveletName = WaveletName.of(waveId, waveletId);
    }

    RobotWaveletData wavelet = openedWavelets.get(waveletName);
    if (wavelet == null) {
      // Open a wavelet from the server
      CommittedWaveletSnapshot snapshot = getWaveletSnapshot(waveletName, participant);
      if (snapshot == null) {
        if (waveletName.waveletId.equals(IdUtil.buildUserDataWaveletId(participant))) {
          // Usually the user data is created by the web client whenever user
          // opens a wavelet for the first time. However, if the wavelet is
          // fetched for the first time with Robot/Data API - user data should be
          // created here.
          wavelet = createEmptyRobotWavelet(participant, waveletName);
        } else {
          throw new InvalidRequestException("Wavelet " + waveletName + " couldn't be retrieved");
        }

      } else {
        ObservableWaveletData obsWavelet = FACTORY.create(snapshot.snapshot);
        wavelet = new RobotWaveletData(obsWavelet, snapshot.committedVersion);
      }
      openedWavelets.put(waveletName, wavelet);
    }
    return wavelet.getOpBasedWavelet(participant);
  }

  @Override
  public OpBasedWavelet openWavelet(OperationRequest operation, ParticipantId participant)
      throws InvalidRequestException {
    try {
      WaveId waveId = ApiIdSerializer.instance().deserialiseWaveId(
          OperationUtil.<String>getRequiredParameter(operation, ParamsProperty.WAVE_ID));
      WaveletId waveletId = ApiIdSerializer.instance().deserialiseWaveletId(
          OperationUtil.<String>getRequiredParameter(operation, ParamsProperty.WAVELET_ID));
      return openWavelet(waveId, waveletId, participant);
    } catch (InvalidIdException e) {
      throw new InvalidRequestException("Invalid id", operation, e);
    }
  }

  @Override
  public ObservableConversationView openConversation(WaveId waveId, WaveletId waveletId,
      ParticipantId participant) throws InvalidRequestException {
    WaveletName waveletName = WaveletName.of(waveId, waveletId);

    if (!openedConversations.containsKey(waveletName)) {
      openedConversations.put(
          waveletName, Maps.<ParticipantId, ObservableConversationView> newHashMap());
    }

    Map<ParticipantId, ObservableConversationView> conversations =
        openedConversations.get(waveletName);

    if (!conversations.containsKey(participant)) {
      OpBasedWavelet wavelet = openWavelet(waveId, waveletId, participant);
      conversations.put(participant, conversationUtil.buildConversation(wavelet));
    }
    return conversations.get(participant);
  }

  @Override
  public ObservableConversationView openConversation(
      OperationRequest operation, ParticipantId participant) throws InvalidRequestException {
    try {
      WaveId waveId = ApiIdSerializer.instance().deserialiseWaveId(
          OperationUtil.<String>getRequiredParameter(operation, ParamsProperty.WAVE_ID));
      WaveletId waveletId = ApiIdSerializer.instance().deserialiseWaveletId(
          OperationUtil.<String>getRequiredParameter(operation, ParamsProperty.WAVELET_ID));
      return openConversation(waveId, waveletId, participant);
    } catch (InvalidIdException e) {
      throw new InvalidRequestException("Invalid id", operation, e);
    }
  }

  // OperationResults implementation begins here

  @Override
  public void putBlip(String blipId, ConversationBlip newBlip) {
    if (blipId.startsWith(TEMP_ID_MARKER)) {
      tempBlipIdMap.put(blipId, newBlip.getId());
    }
  }

  @Override
  public ConversationBlip getBlip(Conversation conversation, String blipId)
      throws InvalidRequestException {
    // We might need to look up the blip id for new blips.
    String actualBlipId = blipId.startsWith(TEMP_ID_MARKER) ? tempBlipIdMap.get(blipId) : blipId;

    ConversationBlip blip = conversation.getBlip(actualBlipId);
    if (blip == null) {
      throw new InvalidRequestException(
          "Blip with id " + blipId + " does not exist or has been deleted");
    }

    return blip;
  }

  // OperationResults implementation begins here

  @Override
  public ConversationUtil getConversationUtil() {
    return conversationUtil;
  }

  @Override
  public Map<String, JsonRpcResponse> getResponses() {
    return Collections.unmodifiableMap(responses);
  }

  @Override
  public JsonRpcResponse getResponse(String operationId) {
    return responses.get(operationId);
  }

  @Override
  public boolean hasResponse(String operationId) {
    return responses.containsKey(operationId);
  }

  @Override
  public ImmutableSet<WaveletId> getVisibleWaveletIds(OperationRequest operation, ParticipantId participant)
      throws InvalidRequestException {
    Set<WaveletId> waveletIds = new HashSet<WaveletId>();
    try {
      WaveId waveId = ApiIdSerializer.instance().deserialiseWaveId(
          OperationUtil.<String>getRequiredParameter(operation, ParamsProperty.WAVE_ID));
      ImmutableSet<WaveletId> ids = waveletProvider.getWaveletIds(waveId);
      for (WaveletId waveletId: ids) {
        WaveletName waveletName = WaveletName.of(waveId, waveletId);
        if (waveletProvider.checkAccessPermission(waveletName, participant)) {
          waveletIds.add(waveletId);
        }
      }
    } catch (InvalidIdException ex) {
      throw new InvalidRequestException("Invalid id", operation, ex);
    } catch (WaveServerException e) {
      LOG.severe("Error of access to wave", e);
    }
    return ImmutableSet.copyOf(waveletIds);
  }

  @Override
  public CommittedWaveletSnapshot getWaveletSnapshot(WaveletName waveletName, ParticipantId participant)
      throws InvalidRequestException {
    try {
      if (!waveletProvider.checkAccessPermission(waveletName, participant)) {
        throw new InvalidRequestException("Access rejected");
      }
      return waveletProvider.getSnapshot(waveletName);
    } catch (WaveServerException ex) {
      LOG.severe("Error of access to wavelet " + waveletName, ex);
      return null;
    }
  }

  @Override
  public void getDeltas(WaveletName waveletName, ParticipantId participant,
      HashedVersion fromVersion, HashedVersion toVersion, Receiver<TransformedWaveletDelta> receiver)
          throws InvalidRequestException {
    try {
      if (!waveletProvider.checkAccessPermission(waveletName, participant)) {
        throw new InvalidRequestException("Access rejected");
      }
      Preconditions.checkState(fromVersion.compareTo(toVersion) <= 0);
      if (fromVersion.equals(toVersion)) {
        return;
      }
      waveletProvider.getHistory(waveletName, fromVersion, toVersion, receiver);
    } catch (WaveServerException ex) {
      LOG.severe("Error of access to wavelet " + waveletName, ex);
    }
  }

  /**
   * Stores a response in this context.
   *
   * @param operationId the id of the robot operation.
   * @param response the response to store.
   */
  private void setResponse(String operationId, JsonRpcResponse response) {
    Preconditions.checkState(
        !responses.containsKey(operationId), "Overwriting an existing response");
    responses.put(operationId, response);
  }
}
TOP

Related Classes of org.waveprotocol.box.server.robots.OperationContextImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.