package org.swellrt.server;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import org.waveprotocol.box.common.Snippets;
import org.waveprotocol.box.server.robots.util.ConversationUtil;
import org.waveprotocol.wave.model.conversation.ObservableConversationView;
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.Doc;
import org.waveprotocol.wave.model.document.Doc.E;
import org.waveprotocol.wave.model.document.Doc.N;
import org.waveprotocol.wave.model.document.Document;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.id.IdConstants;
import org.waveprotocol.wave.model.id.IdGenerator;
import org.waveprotocol.wave.model.id.IdUtil;
import org.waveprotocol.wave.model.supplement.SupplementImpl;
import org.waveprotocol.wave.model.supplement.WaveletBasedSupplement;
import org.waveprotocol.wave.model.wave.ObservableWavelet;
import org.waveprotocol.wave.model.wave.ReadOnlyWaveView;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.ReadableBlipData;
import org.waveprotocol.wave.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
import org.waveprotocol.wave.util.logging.Log;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* A utility class to manage Waves in our own way. Some of the code here is
* taken from or inspired by other parts of the original project.
*
* @author pablojan@gmail.com (Pablo Ojanguren)
*
*/
public class WaveConversationUtils {
private static final Log LOG = Log.get(WaveConversationUtils.class);
private static final int CONVERSATION_SNIPPET_LENGTH = 140;
private static final String TITLE_NOT_AVAILABLE_MSG = "(No title)";
private final IdGenerator idGenerator;
private final ConversationUtil conversationUtil;
@Inject
public WaveConversationUtils(IdGenerator idGenerator, ConversationUtil conversationUtil) {
this.idGenerator = idGenerator;
this.conversationUtil = conversationUtil;
}
/**
* Builds an {@link ObservableConversationView} for the given wavelet. Note
* that this can be expensive since the conversation is not garbage collected
* until the wavelet is.
*
* @param wavelet The wavelet to return the conversation for, must be a valid
* conversation wavelet.
* @throws IllegalArgumentException if the wavelet is not a valid conversation
* wavelet.
*/
public ObservableConversationView buildConversationView(ObservableWavelet wavelet) {
Preconditions.checkArgument(IdUtil.isConversationalId(wavelet.getId()),
"Expected conversational wavelet, got " + wavelet.getId());
Preconditions.checkArgument(WaveletBasedConversation.waveletHasConversation(wavelet),
"Conversation can't be build on a wavelet " + wavelet.getId()
+ " without conversation structure");
ReadOnlyWaveView wv = new ReadOnlyWaveView(wavelet.getWaveId());
wv.addWavelet(wavelet);
return WaveBasedConversationView.create(wv, idGenerator);
}
public String getWaveletConversationSnippet(ReadableWaveletData rawWaveletData) {
String snippet = Snippets.renderSnippet(rawWaveletData, CONVERSATION_SNIPPET_LENGTH).trim();
return snippet;
}
public ObservableConversationView getConversationView(ObservableWaveletData waveletData) {
ObservableConversationView conversationView = null;
OpBasedWavelet wavelet = OpBasedWavelet.createReadOnly(waveletData);
if (WaveletBasedConversation.waveletHasConversation(wavelet)) {
conversationView = conversationUtil.buildConversation(wavelet);
}
return conversationView;
}
public String getConversationFirstLineText(ReadableWaveletData conversationWavelet) {
Preconditions.checkArgument(
conversationWavelet.getDocumentIds().contains(IdConstants.MANIFEST_DOCUMENT_ID),
"Expected conversational wavelet, got " + conversationWavelet.getWaveletId());
String title = "";
try {
Document conversationDoc =
conversationWavelet.getDocument(IdConstants.MANIFEST_DOCUMENT_ID).getContent()
.getMutableDocument();
N conversationNode = conversationDoc.getFirstChild(conversationDoc.getDocumentElement());
N firstBlipNode = conversationDoc.getFirstChild(conversationNode);
String firstBlipId =
conversationDoc.getAttribute(conversationDoc.asElement(firstBlipNode), "id");
Document firstBlipDoc =
conversationWavelet.getDocument(firstBlipId).getContent().getMutableDocument();
E firstLineElement = DocHelper.getElementWithTagName(firstBlipDoc, "line");
E secondLineElement = DocHelper.getNextSiblingElement(firstBlipDoc, firstLineElement);
title =
DocHelper.getText(firstBlipDoc, Point.after(firstBlipDoc, firstLineElement),
Point.before(firstBlipDoc, secondLineElement));
} catch (Exception e) {
LOG.severe("Error extracting conversation title from wavelet "
+ conversationWavelet.getWaveletId());
}
return title;
}
public String getWaveletConversationTitle(ReadableWaveletData conversationWavelet) {
Document conversationDoc =
conversationWavelet.getDocument(IdConstants.MANIFEST_DOCUMENT_ID).getContent()
.getMutableDocument();
if (conversationDoc == null) return TITLE_NOT_AVAILABLE_MSG;
N conversationNode = conversationDoc.getFirstChild(conversationDoc.getDocumentElement());
if (conversationNode == null) return TITLE_NOT_AVAILABLE_MSG;
N firstBlipNode = conversationDoc.getFirstChild(conversationNode);
if (firstBlipNode == null) return TITLE_NOT_AVAILABLE_MSG;
String firstBlipId =
conversationDoc.getAttribute(conversationDoc.asElement(firstBlipNode), "id");
if (firstBlipId == null || firstBlipId.isEmpty()) return TITLE_NOT_AVAILABLE_MSG;
Document firstBlipDoc =
conversationWavelet.getDocument(firstBlipId).getContent().getMutableDocument();
if (firstBlipDoc == null) return TITLE_NOT_AVAILABLE_MSG;
String title = TitleHelper.extractTitle(firstBlipDoc);
if (title.isEmpty())
title = getConversationFirstLineText(conversationWavelet);
if (title.isEmpty()) title = TITLE_NOT_AVAILABLE_MSG;
return title;
}
public Map<String, Long> getConversationBlips(ReadableWaveletData conversationWavelet) {
Preconditions.checkNotNull(conversationWavelet);
Preconditions.checkArgument(IdUtil.isConversationalId(conversationWavelet.getWaveletId()));
HashMap<String, Long> blipsMap = new HashMap<String, Long>();
if (!conversationWavelet.getDocumentIds().contains(
IdUtil.MANIFEST_DOCUMENT_ID))
return blipsMap;
ReadableBlipData manifestBlip = conversationWavelet.getDocument(IdUtil.MANIFEST_DOCUMENT_ID);
Document manifestDoc = manifestBlip.getContent().getMutableDocument();
List<Doc.E> blipElements = ExtendedDocHelper.getAllElementsByTagName("blip", manifestDoc);
for (Doc.E element : blipElements) {
String blipId = manifestDoc.getAttribute(element, "id");
Long blipVersion = null;
if (conversationWavelet.getDocumentIds().contains(blipId)) {
blipVersion = conversationWavelet.getDocument(blipId).getLastModifiedVersion();
blipsMap.put(blipId, blipVersion);
}
}
return blipsMap;
}
public Map<String, Long> getUserDataBlips(ReadableWaveletData userDataWavelet) {
Preconditions.checkNotNull(userDataWavelet);
Preconditions.checkArgument(IdUtil.isUserDataWavelet(userDataWavelet.getWaveletId()));
/**
* We suppose to have only one conversation (conv+root), so we are counting
* all the blip tags in the documento.
*/
HashMap<String, Long> blipsMap = new HashMap<String, Long>();
if (!userDataWavelet.getDocumentIds().contains(WaveletBasedSupplement.READSTATE_DOCUMENT))
return blipsMap;
ReadableBlipData manifestBlip =
userDataWavelet.getDocument(WaveletBasedSupplement.READSTATE_DOCUMENT);
Document manifestDoc = manifestBlip.getContent().getMutableDocument();
List<Doc.E> blipElements = ExtendedDocHelper.getAllElementsByTagName("blip", manifestDoc);
for (Doc.E element : blipElements) {
String blipId = manifestDoc.getAttribute(element, "i");
String blipVersion = manifestDoc.getAttribute(element, "v");
try {
blipsMap.put(blipId, Long.valueOf(blipVersion));
} catch (NumberFormatException e) {
}
}
return blipsMap;
}
/**
* Calculates the number of blips not read yet by an user. It's a simplistic
* implementation based on {@link SupplementImpl.isBlipUnread}. It doesn't
* take care of wavelet-override version.
*
* @param contentBlips
* @param userBlips
* @return
*/
public int getNotReadBlips(Map<String, Long> contentBlips, Map<String, Long> userBlips) {
int nonReadCount = contentBlips.size();
for (Entry<String, Long> docBlip : contentBlips.entrySet()) {
if (userBlips.containsKey(docBlip.getKey())) {
Long blipUserVersion = userBlips.get(docBlip.getKey());
if (blipUserVersion != null && blipUserVersion >= docBlip.getValue()) nonReadCount--;
}
}
return nonReadCount;
}
}