Package org.waveprotocol.box.server.waveserver

Source Code of org.waveprotocol.box.server.waveserver.WaveDigester

/**
* 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.waveserver;

import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.google.wave.api.ApiIdSerializer;
import com.google.wave.api.SearchResult;
import com.google.wave.api.SearchResult.Digest;

import org.waveprotocol.box.common.Snippets;
import org.waveprotocol.box.server.robots.util.ConversationUtil;
import org.waveprotocol.wave.model.conversation.BlipIterators;
import org.waveprotocol.wave.model.conversation.ConversationBlip;
import org.waveprotocol.wave.model.conversation.ObservableConversation;
import org.waveprotocol.wave.model.conversation.ObservableConversationBlip;
import org.waveprotocol.wave.model.conversation.ObservableConversationView;
import org.waveprotocol.wave.model.conversation.TitleHelper;
import org.waveprotocol.wave.model.conversation.WaveletBasedConversation;
import org.waveprotocol.wave.model.document.Document;
import org.waveprotocol.wave.model.id.IdUtil;
import org.waveprotocol.wave.model.id.ModernIdSerialiser;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.supplement.PrimitiveSupplement;
import org.waveprotocol.wave.model.supplement.PrimitiveSupplementImpl;
import org.waveprotocol.wave.model.supplement.SupplementedWave;
import org.waveprotocol.wave.model.supplement.SupplementedWaveImpl;
import org.waveprotocol.wave.model.supplement.SupplementedWaveImpl.DefaultFollow;
import org.waveprotocol.wave.model.supplement.WaveletBasedSupplement;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.WaveViewData;
import org.waveprotocol.wave.model.wave.data.WaveletData;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* Generates digests for the search service.
*
* @author yurize@apache.org
*/
public class WaveDigester {

  private final ConversationUtil conversationUtil;
  private static final int DIGEST_SNIPPET_LENGTH = 140;
  private static final int PARTICIPANTS_SNIPPET_LENGTH = 5;
  private static final String EMPTY_WAVELET_TITLE = "";

  @Inject
  public WaveDigester(ConversationUtil conversationUtil) {
    this.conversationUtil = conversationUtil;
  }

  public SearchResult generateSearchResult(ParticipantId participant, String query,
      Collection<WaveViewData> results) {
    // Generate exactly one digest per wave. This includes conversational and
    // non-conversational waves. The position-based API for search prevents the
    // luxury of extra filtering here. Filtering can only be done in the
    // searchProvider. All waves returned by the search provider must be
    // included in the search result.
    SearchResult result = new SearchResult(query);
    if (results == null) {
      return result;
    }
    for (WaveViewData wave : results) {
      result.addDigest(build(participant, wave));
    }

    assert result.getDigests().size() == results.size();
    return result;
  }

  public Digest build(ParticipantId participant, WaveViewData wave) {

    Digest digest;

    // Note: the indexing infrastructure only supports single-conversation
    // waves, and requires raw wavelet access for snippeting.
    ObservableWaveletData root = null;
    ObservableWaveletData other = null;
    ObservableWaveletData udw = null;
    for (ObservableWaveletData waveletData : wave.getWavelets()) {
      WaveletId waveletId = waveletData.getWaveletId();
      if (IdUtil.isConversationRootWaveletId(waveletId)) {
        root = waveletData;
      } else if (IdUtil.isConversationalId(waveletId)) {
        other = waveletData;
      } else if (IdUtil.isUserDataWavelet(waveletId)
          && waveletData.getCreator().equals(participant)) {
        udw = waveletData;
      }
    }

    ObservableWaveletData convWavelet = root != null ? root : other;
    SupplementedWave supplement = null;
    ObservableConversationView conversations = null;
    if (convWavelet != null) {
      OpBasedWavelet wavelet = OpBasedWavelet.createReadOnly(convWavelet);
      if (WaveletBasedConversation.waveletHasConversation(wavelet)) {
        conversations = conversationUtil.buildConversation(wavelet);
        supplement = buildSupplement(participant, conversations, udw);
      }
    }
    if (conversations != null) {
      // This is a conversational wave. Produce a conversational digest.
      digest = generateDigest(conversations, supplement, convWavelet);
    } else {
      // It is unknown how to present this wave.
      digest = generateEmptyorUnknownDigest(wave);
    }

    return digest;
  }

  /**
   * Produces a digest for a set of conversations. Never returns null.
   *
   * @param conversations the conversation.
   * @param supplement the supplement that allows to easily perform various
   *        queries on user related state of the wavelet.
   * @param rawWaveletData the waveletData from which the digest is generated.
   *        This wavelet is a copy.
   * @return the server representation of the digest for the query.
   */
  Digest generateDigest(ObservableConversationView conversations, SupplementedWave supplement,
      WaveletData rawWaveletData) {
    ObservableConversation rootConversation = conversations.getRoot();
    ObservableConversationBlip firstBlip = null;
    if ((rootConversation != null) && (rootConversation.getRootThread() != null)
        && (rootConversation.getRootThread().getFirstBlip() != null)) {
      firstBlip = rootConversation.getRootThread().getFirstBlip();
    }
    String title;
    if (firstBlip != null) {
      Document firstBlipContents = firstBlip.getContent();
      title = TitleHelper.extractTitle(firstBlipContents).trim();
    } else {
      title = EMPTY_WAVELET_TITLE;
    }

    String snippet = Snippets.renderSnippet(rawWaveletData, DIGEST_SNIPPET_LENGTH).trim();
    if (snippet.startsWith(title) && !title.isEmpty()) {
      // Strip the title from the snippet if the snippet starts with the title.
      snippet = snippet.substring(title.length());
    }
    String waveId = ApiIdSerializer.instance().serialiseWaveId(rawWaveletData.getWaveId());
    List<String> participants = CollectionUtils.newArrayList();
    for (ParticipantId p : rawWaveletData.getParticipants()) {
      if (participants.size() < PARTICIPANTS_SNIPPET_LENGTH) {
        participants.add(p.getAddress());
      } else {
        break;
      }
    }
    int unreadCount = 0;
    int blipCount = 0;
    long lastModified = -1;
    for (ConversationBlip blip : BlipIterators.breadthFirst(rootConversation)) {
      if (supplement.isUnread(blip)) {
        unreadCount++;
      }
      lastModified = Math.max(blip.getLastModifiedTime(), lastModified);
      blipCount++;
    }
    return new Digest(title, snippet, waveId, participants, lastModified,
        rawWaveletData.getCreationTime(), unreadCount, blipCount);
  }

  /** @return a digest for an empty wave. */
  private Digest emptyDigest(WaveViewData wave) {
    String title = ModernIdSerialiser.INSTANCE.serialiseWaveId(wave.getWaveId());
    String id = ApiIdSerializer.instance().serialiseWaveId(wave.getWaveId());
    return new Digest(title, "(empty)", id, Collections.<String> emptyList(), -1L, -1L, 0, 0);
  }

  /** @return a digest for an unrecognised type of wave. */
  private Digest unknownDigest(WaveViewData wave) {
    String title = ModernIdSerialiser.INSTANCE.serialiseWaveId(wave.getWaveId());
    String id = ApiIdSerializer.instance().serialiseWaveId(wave.getWaveId());
    long lmt = -1L;
    long created = -1L;
    int docs = 0;
    List<String> participants = new ArrayList<String>();
    for (WaveletData data : wave.getWavelets()) {
      lmt = Math.max(lmt, data.getLastModifiedTime());
      created = Math.max(lmt, data.getCreationTime());
      docs += data.getDocumentIds().size();

      for (ParticipantId p : data.getParticipants()) {
        if (participants.size() < PARTICIPANTS_SNIPPET_LENGTH) {
          participants.add(p.getAddress());
        } else {
          break;
        }
      }
    }
    return new Digest(title, "(unknown)", id, participants, lmt, created, 0, docs);
  }

  /**
   * Generates an empty digest in case the wave is empty, or an unknown digest
   * otherwise.
   *
   * @param wave the wave.
   * @return the generated digest.
   */
  Digest generateEmptyorUnknownDigest(WaveViewData wave) {
    boolean empty = !wave.getWavelets().iterator().hasNext();
    Digest digest = empty ? emptyDigest(wave) : unknownDigest(wave);
    return digest;
  }

  /**
   * Builds the supplement model from a wave. Never returns null.
   *
   * @param viewer the participant for which the supplement is constructed.
   * @param conversations conversations in the wave
   * @param udw the user data wavelet for the logged user.
   * @return the wave supplement.
   */
  @VisibleForTesting
  SupplementedWave buildSupplement(ParticipantId viewer, ObservableConversationView conversations,
      ObservableWaveletData udw) {
    // Use mock state if there is no UDW.
    PrimitiveSupplement udwState =
        udw != null ? WaveletBasedSupplement.create(OpBasedWavelet.createReadOnly(udw))
            : new PrimitiveSupplementImpl();
        return SupplementedWaveImpl.create(udwState, conversations, viewer, DefaultFollow.ALWAYS);
  }
}
TOP

Related Classes of org.waveprotocol.box.server.waveserver.WaveDigester

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.