Author: yurize
Date: Sat Nov 26 19:27:56 2011
New Revision: 1206549

URL: http://svn.apache.org/viewvc?rev=1206549&view=rev
Log:
Adds  some files forgotten in previous commit

Added:
    
incubator/wave/trunk/src/org/waveprotocol/box/server/waveserver/WaveDigester.java
    
incubator/wave/trunk/test/org/waveprotocol/box/server/robots/operations/TestingWaveletData.java
    
incubator/wave/trunk/test/org/waveprotocol/box/server/waveserver/WaveDigesterTest.java

Added: 
incubator/wave/trunk/src/org/waveprotocol/box/server/waveserver/WaveDigester.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/waveserver/WaveDigester.java?rev=1206549&view=auto
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/server/waveserver/WaveDigester.java
 (added)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/server/waveserver/WaveDigester.java
 Sat Nov 26 19:27:56 2011
@@ -0,0 +1,239 @@
+/**
+ * Copyright 2011 Google Inc.
+ *
+ * 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.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.WaveletBasedSupplement;
+import 
org.waveprotocol.wave.model.supplement.SupplementedWaveImpl.DefaultFollow;
+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 [email protected]
+ */
+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) {
+      // 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)) {
+          // assume this is the user data wavelet for the right user.
+          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.
+        result.addDigest(generateDigest(conversations, supplement, 
convWavelet));
+      } else {
+        // It is unknown how to present this wave.
+        result.addDigest(generateEmptyorUnknownDigest(wave));
+      }
+    }
+
+    assert result.getDigests().size() == results.size();
+    return result;
+  }
+
+  /**
+   * 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;
+    for (ConversationBlip blip : BlipIterators.breadthFirst(rootConversation)) 
{
+      if (supplement.isUnread(blip)) {
+        unreadCount++;
+      }
+      blipCount++;
+    }
+    return new Digest(title, snippet, waveId, participants, 
rawWaveletData.getLastModifiedTime(),
+        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);
+  }
+}

Added: 
incubator/wave/trunk/test/org/waveprotocol/box/server/robots/operations/TestingWaveletData.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/test/org/waveprotocol/box/server/robots/operations/TestingWaveletData.java?rev=1206549&view=auto
==============================================================================
--- 
incubator/wave/trunk/test/org/waveprotocol/box/server/robots/operations/TestingWaveletData.java
 (added)
+++ 
incubator/wave/trunk/test/org/waveprotocol/box/server/robots/operations/TestingWaveletData.java
 Sat Nov 26 19:27:56 2011
@@ -0,0 +1,109 @@
+/**
+ * Copyright 2011 Google Inc.
+ *
+ * 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.robots.operations;
+
+import com.google.common.collect.ImmutableList;
+
+import org.waveprotocol.box.server.util.WaveletDataUtil;
+import org.waveprotocol.wave.model.conversation.Conversation;
+import org.waveprotocol.wave.model.conversation.ConversationBlip;
+import org.waveprotocol.wave.model.conversation.ConversationView;
+import org.waveprotocol.wave.model.conversation.TitleHelper;
+import org.waveprotocol.wave.model.conversation.WaveBasedConversationView;
+import org.waveprotocol.wave.model.conversation.WaveletBasedConversation;
+import org.waveprotocol.wave.model.document.util.LineContainers;
+import org.waveprotocol.wave.model.document.util.XmlStringBuilder;
+import org.waveprotocol.wave.model.id.WaveId;
+import org.waveprotocol.wave.model.id.WaveletId;
+import org.waveprotocol.wave.model.operation.SilentOperationSink;
+import 
org.waveprotocol.wave.model.operation.wave.BasicWaveletOperationContextFactory;
+import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
+import org.waveprotocol.wave.model.testing.BasicFactories;
+import org.waveprotocol.wave.model.testing.FakeIdGenerator;
+import org.waveprotocol.wave.model.version.HashedVersion;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.model.wave.ParticipationHelper;
+import org.waveprotocol.wave.model.wave.ReadOnlyWaveView;
+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.data.impl.WaveViewDataImpl;
+import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
+import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
+
+import java.util.List;
+
+/**
+ * Builds a wavelet and provides direct access to the various layers of
+ * abstraction.
+ * 
+ * @author [email protected] (Joseph Gentle)
+ */
+public class TestingWaveletData {
+  private final ObservableWaveletData waveletData;
+  private final ObservableWaveletData userWaveletData;
+  private final Conversation conversation;
+  private final WaveViewData waveViewData;
+
+  public TestingWaveletData(
+      WaveId waveId, WaveletId waveletId, ParticipantId author, boolean 
isConversational) {
+    waveletData =
+        new WaveletDataImpl(waveletId, author, 1234567890, 0, 
HashedVersion.unsigned(0), 0,
+            waveId, 
BasicFactories.observablePluggableMutableDocumentFactory());
+    userWaveletData =
+        new WaveletDataImpl(WaveletId.of("example.com", 
"[email protected]"), author,
+            1234567890, 0, HashedVersion.unsigned(0), 0,
+          waveId, BasicFactories.observablePluggableMutableDocumentFactory());
+    
+    OpBasedWavelet wavelet =
+      new OpBasedWavelet(waveId, waveletData, new 
BasicWaveletOperationContextFactory(author),
+          ParticipationHelper.DEFAULT,
+          SilentOperationSink.Executor.<WaveletOperation, 
WaveletData>build(waveletData),
+          SilentOperationSink.VOID);
+    ReadOnlyWaveView waveView = new ReadOnlyWaveView(waveId);
+    waveView.addWavelet(wavelet);
+    
+    if (isConversational) {
+      ConversationView conversationView = 
WaveBasedConversationView.create(waveView, FakeIdGenerator.create());
+      WaveletBasedConversation.makeWaveletConversational(wavelet);
+      conversation = conversationView.getRoot();
+
+      conversation.addParticipant(author);
+    } else {
+      conversation = null;
+    }
+
+    waveViewData = WaveViewDataImpl.create(waveId, 
ImmutableList.of(waveletData, userWaveletData));
+  }
+
+  public void appendBlipWithText(String text) {
+    ConversationBlip blip = conversation.getRootThread().appendBlip();
+    LineContainers.appendToLastLine(blip.getContent(), 
XmlStringBuilder.createText(text));
+    TitleHelper.maybeFindAndSetImplicitTitle(blip.getContent());
+  }
+
+  public List<ObservableWaveletData> copyWaveletData() {
+    // This data object already has an op-based owner on top. Must copy it.
+    return ImmutableList.of(WaveletDataUtil.copyWavelet(waveletData),
+        WaveletDataUtil.copyWavelet(userWaveletData));
+  }
+
+  public WaveViewData copyViewData() {
+    return WaveViewDataImpl.create(waveViewData.getWaveId(),copyWaveletData());
+  }
+}

Added: 
incubator/wave/trunk/test/org/waveprotocol/box/server/waveserver/WaveDigesterTest.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/test/org/waveprotocol/box/server/waveserver/WaveDigesterTest.java?rev=1206549&view=auto
==============================================================================
--- 
incubator/wave/trunk/test/org/waveprotocol/box/server/waveserver/WaveDigesterTest.java
 (added)
+++ 
incubator/wave/trunk/test/org/waveprotocol/box/server/waveserver/WaveDigesterTest.java
 Sat Nov 26 19:27:56 2011
@@ -0,0 +1,101 @@
+/**
+ * Copyright 2011 Google Inc.
+ *
+ * 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 static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static 
org.waveprotocol.box.server.util.testing.TestingConstants.PARTICIPANT;
+import static 
org.waveprotocol.box.server.util.testing.TestingConstants.WAVE_ID;
+
+import com.google.wave.api.SearchResult.Digest;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.waveprotocol.box.server.robots.operations.TestingWaveletData;
+import org.waveprotocol.box.server.robots.util.ConversationUtil;
+import org.waveprotocol.wave.model.conversation.ConversationBlip;
+import org.waveprotocol.wave.model.conversation.ObservableConversationView;
+import org.waveprotocol.wave.model.id.IdGenerator;
+import org.waveprotocol.wave.model.id.WaveletId;
+import org.waveprotocol.wave.model.supplement.SupplementedWave;
+import org.waveprotocol.wave.model.wave.ObservableWavelet;
+import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
+import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
+
+/**
+ * Unit tests for {@link WaveDigester}.
+ * 
+ * @author [email protected] (Yuri Zelikov)
+ */
+public class WaveDigesterTest extends TestCase {
+
+  private static final WaveletId CONVERSATION_WAVELET_ID = 
WaveletId.of("example.com", "conv+root");
+
+  @Mock
+  private IdGenerator idGenerator;
+
+  private ConversationUtil conversationUtil;
+
+  private WaveDigester digester;
+
+  @Override
+  protected void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    conversationUtil = new ConversationUtil(idGenerator);
+    digester = new WaveDigester(conversationUtil);
+  }
+
+  public void testWaveletWithNoBlipsResultsInEmptyTitleAndNoBlips() {
+    TestingWaveletData data =
+        new TestingWaveletData(WAVE_ID, CONVERSATION_WAVELET_ID, PARTICIPANT, 
true);
+    ObservableWaveletData observableWaveletData = 
data.copyWaveletData().get(0);
+    ObservableWavelet wavelet = 
OpBasedWavelet.createReadOnly(observableWaveletData);
+    ObservableConversationView conversation = 
conversationUtil.buildConversation(wavelet);
+
+    SupplementedWave supplement = mock(SupplementedWave.class);
+    when(supplement.isUnread(any(ConversationBlip.class))).thenReturn(true);
+
+    Digest digest = digester.generateDigest(conversation, supplement, 
observableWaveletData);
+
+    assertEquals("", digest.getTitle());
+    assertEquals(digest.getBlipCount(), 0);
+  }
+
+
+  public void testWaveletWithBlipsResultsInNonEmptyTitle() {
+    TestingWaveletData data =
+        new TestingWaveletData(WAVE_ID, CONVERSATION_WAVELET_ID, PARTICIPANT, 
true);
+    String title = "title";
+    data.appendBlipWithText(title);
+    ObservableWaveletData observableWaveletData = 
data.copyWaveletData().get(0);
+    ObservableWavelet wavelet = 
OpBasedWavelet.createReadOnly(observableWaveletData);
+    ObservableConversationView conversation = 
conversationUtil.buildConversation(wavelet);
+
+    SupplementedWave supplement = mock(SupplementedWave.class);
+    when(supplement.isUnread(any(ConversationBlip.class))).thenReturn(true);
+
+    Digest digest = digester.generateDigest(conversation, supplement, 
observableWaveletData);
+
+    assertEquals(title, digest.getTitle());
+    assertEquals(digest.getBlipCount(), 1);
+  }
+}


Reply via email to