/**
* 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 com.google.wave.api;
import com.google.wave.api.JsonRpcConstant.ParamsProperty;
import com.google.wave.api.OperationRequest.Parameter;
import com.google.wave.api.impl.RawAttachmentData;
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.media.model.AttachmentId;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* A utility class that abstracts the queuing of operations, represented by
* {@link OperationRequest}, using easily callable functions. The
* {@link OperationQueue} queues the resulting operations in order.
*
* Typically there shouldn't be a need to call this directly unless operations
* are needed on entities outside of the scope of the robot. For example, to
* modify a blip that does not exist in the current context, you might specify
* the wave, wavelet, and blip id to generate an operation.
*
* Any calls to this will not be reflected in the robot in any way. For example,
* calling wavelet_append_blip will not result in a new blip being added to the
* robot current context, only an operation to be sent to the robot proxy.
*/
public class OperationQueue implements Serializable {
/** A random number generator for the temporary ids. */
private static final Random ID_GENERATOR = new Random();
/** The format of temporary blip ids. */
private static final String TEMP_BLIP_ID_FORMAT = "TBD_%s_%s";
/** The format of temporary wave ids. */
private static final String TEMP_WAVE_ID_FORMAT = "%s!TBD_%s";
/** The format of wavelet ids. */
private static final String TEMP_WAVELET_ID_FORMAT = "%s!conv+root";
/** The format of new operation ids. */
private static final String OP_ID_FORMAT = "op%d";
/** Some class global counters. */
private static long nextOpId = 1;
/** The id that can be set for {@code proxyingFor} parameter. */
private final String proxyForId;
/** The operation queue. */
private List<OperationRequest> pendingOperations;
/**
* Constructor that creates a new instance of {@link OperationQueue} with
* an empty queue and no proxying information set.
*/
public OperationQueue() {
this(new ArrayList<OperationRequest>(), null);
}
/**
* Constructor that creates a new instance of {@link OperationQueue} with
* a specified proxying information.
*
* @param proxyForId the proxying information.
*/
public OperationQueue(String proxyForId) {
this(new ArrayList<OperationRequest>(), proxyForId);
}
/**
* Constructor that creates a new instance of {@link OperationQueue} with
* a specified queue and proxying information.
*
* @param operations the underlying operation queue that should be used for
* this {@link OperationQueue}.
* @param proxyForId the proxying information.
*/
public OperationQueue(List<OperationRequest> operations, String proxyForId) {
this.pendingOperations = operations;
this.proxyForId = proxyForId;
}
/**
* Returns a list of the pending operations that have been queued up.
*
* @return the pending operations.
*/
public List<OperationRequest> getPendingOperations() {
return pendingOperations;
}
/**
* Returns the id for {@code proxyingFor} parameter.
*
* @return the proxying id.
*/
public String getProxyForId() {
return proxyForId;
}
/**
* Creates a view of this {@link OperationQueue} with the proxying for set to
* the given id.
*
* This method returns a new instance of an operation queue that shares the
* operation list, but has a different {@link #proxyForId} set so when the
* robot uses this new queue, subsequent operations will be sent out with the
* {@code proxying_for} field set.
*
* @param proxyForId the proxying information.
* @return a view of this {@link OperationQueue} with the proxying information
* set.
*/
public OperationQueue proxyFor(String proxyForId) {
return new OperationQueue(pendingOperations, proxyForId);
}
/**
* Clears this operation queue.
*/
public void clear() {
pendingOperations.clear();
}
/**
* Appends a blip to a wavelet.
*
* @param wavelet the wavelet to append the new blip to.
* @param initialContent the initial content of the new blip.
* @return an instance of {@link Blip} that represents the new blip.
*/
public Blip appendBlipToWavelet(Wavelet wavelet, String initialContent) {
Blip newBlip = newBlip(wavelet, initialContent, null, generateTempBlipId(wavelet),
wavelet.getRootThread().getId());
appendOperation(OperationType.WAVELET_APPEND_BLIP, wavelet,
Parameter.of(ParamsProperty.BLIP_DATA, newBlip.serialize()));
return newBlip;
}
/**
* Adds a participant to a wavelet.
*
* @param wavelet the wavelet that the new participant should be added to.
* @param participantId the id of the new participant.
*/
public void addParticipantToWavelet(Wavelet wavelet, String participantId) {
appendOperation(OperationType.WAVELET_ADD_PARTICIPANT_NEWSYNTAX, wavelet,
Parameter.of(ParamsProperty.PARTICIPANT_ID, participantId));
}
/**
* Removes a participant from a wavelet.
*
* @param wavelet the wavelet that the participant should be removed from.
* @param participantId the id of the participant to be removed.
*/
public void removeParticipantFromWavelet(Wavelet wavelet, String participantId) {
appendOperation(OperationType.WAVELET_REMOVE_PARTICIPANT_NEWSYNTAX, wavelet,
Parameter.of(ParamsProperty.PARTICIPANT_ID, participantId));
}
/**
* Creates a new wavelet.
*
* @param domain the domain to create the wavelet in.
* @param participants the initial participants on this new wavelet.
* @return an instance of {@link Wavelet} that represents the new wavelet.
*/
public Wavelet createWavelet(String domain, Set<String> participants) {
return createWavelet(domain, participants, "");
}
/**
* Creates a new wavelet with an optional message.
*
* @param domain the domain to create the wavelet in.
* @param participants the initial participants on this new wavelet.
* @param message an optional payload that is returned with the corresponding
* event.
* @return an instance of {@link Wavelet} that represents the new wavelet.
*/
public Wavelet createWavelet(String domain, Set<String> participants, String message) {
Wavelet newWavelet = newWavelet(domain, participants, this);
OperationRequest operation = appendOperation(OperationType.ROBOT_CREATE_WAVELET,
newWavelet, Parameter.of(ParamsProperty.WAVELET_DATA, newWavelet.serialize()));
// Don't add the message if it's null or empty.
if (message != null && !message.isEmpty()) {
operation.addParameter(Parameter.of(ParamsProperty.MESSAGE, message));
}
return newWavelet;
}
/**
* Appends search operation for specified query.
*
* @param query the query to execute.
* @param index the index from which to return results.
* @param numresults the number of results to return.
*/
public void search(String query, Integer index, Integer numresults) {
Parameter queryParam = Parameter.of(ParamsProperty.QUERY, query);
Parameter indexParam = Parameter.of(ParamsProperty.INDEX, index);
Parameter numresultsParam = Parameter.of(ParamsProperty.NUM_RESULTS, numresults);
appendOperation(OperationType.ROBOT_SEARCH, queryParam, indexParam, numresultsParam);
}
/**
* Sets a key-value pair on the data document of a wavelet.
*
* @param wavelet to set the data document on.
* @param name the name of this data.
* @param value the value of this data.
*/
public void setDatadocOfWavelet(Wavelet wavelet, String name, String value) {
appendOperation(OperationType.WAVELET_SET_DATADOC, wavelet,
Parameter.of(ParamsProperty.DATADOC_NAME, name),
Parameter.of(ParamsProperty.DATADOC_VALUE, value));
}
/**
* Requests a snapshot of the specified wave.
*
* @param waveId the id of the wave that should be fetched.
* @param waveletId the wavelet id that should be fetched.
*/
public void fetchWavelet(WaveId waveId, WaveletId waveletId) {
appendOperation(OperationType.ROBOT_FETCH_WAVE, waveId, waveletId, null);
}
/**
* Retrieves list of wave wavelets ids.
*
* @param waveId the id of the wave.
*/
public void retrieveWaveletIds(WaveId waveId) {
appendOperation(OperationType.ROBOT_FETCH_WAVE, waveId, null, null,
Parameter.of(ParamsProperty.RETURN_WAVELET_IDS, true));
}
/**
* Exports snapshot of wavelet.
*
* @param waveId the id of the wave that should be exported.
* @param waveletId the id of the wavelet that should be exported.
*/
public void exportSnapshot(WaveId waveId, WaveletId waveletId) {
appendOperation(OperationType.ROBOT_EXPORT_SNAPSHOT, waveId, waveletId, null);
}
/**
* Exports deltas of wavelet.
*
* @param waveId the id of the wave that should be exported.
* @param waveletId the id of the wavelet that should be exported.
* @param fromVersion start version.
* @param toVersion to version.
*/
public void exportRawDeltas(WaveId waveId, WaveletId waveletId,
byte[] fromVersion, byte[] toVersion) {
appendOperation(OperationType.ROBOT_EXPORT_DELTAS, waveId, waveletId, null,
Parameter.of(ParamsProperty.FROM_VERSION, fromVersion),
Parameter.of(ParamsProperty.TO_VERSION, toVersion));
}
/**
* Export attachment.
*
* @param attachmentId the id of attachment.
*/
public void exportAttachment(AttachmentId attachmentId) {
appendOperation(OperationType.ROBOT_EXPORT_ATTACHMENT,
Parameter.of(ParamsProperty.ATTACHMENT_ID, attachmentId.serialise()));
}
/**
* Imports deltas of wavelet.
*
* @param waveId the id of the wave that should be imported.
* @param waveletId the id of the wavelet that should be imported.
* @param history the history in deltas.
*/
public void importRawDeltas(WaveId waveId, WaveletId waveletId, List<byte[]> history) {
appendOperation(OperationType.ROBOT_IMPORT_DELTAS,
waveId, waveletId, null, Parameter.of(ParamsProperty.RAW_DELTAS, history));
}
/**
* Imports attachment.
*
* @param waveId the id of the wave that should be imported.
* @param waveletId the id of the wavelet that should be imported.
* @param attachmentId the id of attachment.
* @param attachmentData the attachment data.
*/
public void importAttachment(WaveId waveId, WaveletId waveletId,
AttachmentId attachmentId, RawAttachmentData attachmentData) {
appendOperation(OperationType.ROBOT_IMPORT_ATTACHMENT,
waveId, waveletId, null,
Parameter.of(ParamsProperty.ATTACHMENT_ID, attachmentId.serialise()),
Parameter.of(ParamsProperty.ATTACHMENT_DATA, attachmentData));
}
/**
* Sets the title of a wavelet.
*
* @param wavelet the wavelet whose title will be changed.
* @param title the new title to be set.
*/
public void setTitleOfWavelet(Wavelet wavelet, String title) {
appendOperation(OperationType.WAVELET_SET_TITLE, wavelet,
Parameter.of(ParamsProperty.WAVELET_TITLE, title));
}
/**
* Modifies a tag in a wavelet.
*
* @param wavelet the wavelet to modify the tag from.
* @param tag the name of the tag to be modified
* @param modifyHow how to modify the tag. The default behavior is to add the
* tag. Specify {@code remove} to remove, or specify {@code null} or
* {@code add} to add.
*/
public void modifyTagOfWavelet(Wavelet wavelet, String tag, String modifyHow) {
appendOperation(OperationType.WAVELET_MODIFY_TAG, wavelet,
Parameter.of(ParamsProperty.NAME, tag),
Parameter.of(ParamsProperty.MODIFY_HOW, modifyHow));
}
/**
* Creates a child blip of another blip.
*
* @param blip the parent blip.
* @return an instance of {@link Blip} that represents the new child blip.
*/
public Blip createChildOfBlip(Blip blip) {
// Create a new thread.
String tempBlipId = generateTempBlipId(blip.getWavelet());
Wavelet wavelet = blip.getWavelet();
BlipThread thread = new BlipThread(tempBlipId, -1, new ArrayList<String>(),
wavelet.getBlips());
// Add the new thread to the blip and wavelet.
blip.addThread(thread);
wavelet.addThread(thread);
// Create a new blip in the new thread.
Blip newBlip = newBlip(blip.getWavelet(), "", blip.getBlipId(), tempBlipId, thread.getId());
appendOperation(OperationType.BLIP_CREATE_CHILD, blip,
Parameter.of(ParamsProperty.BLIP_DATA, newBlip.serialize()));
return newBlip;
}
/**
* Appends a new blip to the end of the thread of the given blip.
*
* @param blip the blip whose thread will be appended.
* @return an instance of {@link Blip} that represents the new blip.
*/
public Blip continueThreadOfBlip(Blip blip) {
Blip newBlip = newBlip(blip.getWavelet(), "", blip.getParentBlipId(),
generateTempBlipId(blip.getWavelet()), blip.getThread().getId());
appendOperation(OperationType.BLIP_CONTINUE_THREAD, blip,
Parameter.of(ParamsProperty.BLIP_DATA, newBlip.serialize()));
return newBlip;
}
/**
* Deletes the specified blip.
*
* @param wavelet the wavelet that owns the blip.
* @param blipId the id of the blip that will be deleted.
*/
public void deleteBlip(Wavelet wavelet, String blipId) {
appendOperation(OperationType.BLIP_DELETE, wavelet.getWaveId(), wavelet.getWaveletId(),
blipId);
}
/**
* Appends content with markup to a blip.
*
* @param blip the blip where this markup content should be added to.
* @param content the markup content that should be added to the blip.
*/
public void appendMarkupToDocument(Blip blip, String content) {
appendOperation(OperationType.DOCUMENT_APPEND_MARKUP, blip,
Parameter.of(ParamsProperty.CONTENT, content));
}
/**
* Submits this operation queue when the given {@code other} operation queue
* is submitted.
*
* @param other the other operation queue to merge this operation queue with.
*/
public void submitWith(OperationQueue other) {
other.pendingOperations.addAll(this.pendingOperations);
this.pendingOperations = other.pendingOperations;
}
/**
* Creates and queues a document modify operation.
*
* @param blip the blip to modify.
* @return an instance of {@code OperationRequest} that represents this
* operation. The caller of this method should append the required and/or
* optional parameters, such as:
* <ul>
* <li>{@code modifyAction}</li>
* <li>{@code modifyQuery}</li>
* <li>{@code index}</li>
* <li>{@code range}</li>
* </ul>
*/
public OperationRequest modifyDocument(Blip blip) {
return appendOperation(OperationType.DOCUMENT_MODIFY, blip);
}
/**
* Inserts a new inline blip at a specified location.
*
* @param blip the blip to anchor this inline blip from.
* @param position the position in the given blip to insert this new inline
* blip.
* @return an instance of {@link Blip} that represents the inline blip.
*/
public Blip insertInlineBlipToDocument(Blip blip, int position) {
// Create a new thread.
String tempBlipId = generateTempBlipId(blip.getWavelet());
Wavelet wavelet = blip.getWavelet();
BlipThread thread = new BlipThread(tempBlipId, position, new ArrayList<String>(),
wavelet.getBlips());
// Add the new thread to the blip and wavelet.
blip.addThread(thread);
wavelet.addThread(thread);
// Create a new blip in the new thread.
Blip inlineBlip = newBlip(blip.getWavelet(), "", blip.getBlipId(), tempBlipId,
thread.getId());
appendOperation(OperationType.DOCUMENT_INSERT_INLINE_BLIP, blip,
Parameter.of(ParamsProperty.INDEX, position),
Parameter.of(ParamsProperty.BLIP_DATA, inlineBlip.serialize()));
return inlineBlip;
}
/**
* Creates and appends a new operation to the operation queue.
*
* @param opType the type of the operation.
* @param parameters the parameters that should be added as a property of
* the operation.
* @return an instance of {@link OperationRequest} that represents the queued
* operation.
*/
OperationRequest appendOperation(OperationType opType, Parameter... parameters) {
return appendOperation(opType, null, null, null, parameters);
}
/**
* Creates and appends a new operation to the operation queue.
*
* @param opType the type of the operation.
* @param wavelet the wavelet to apply the operation to.
* @param parameters the parameters that should be added as a property of
* the operation.
* @return an instance of {@link OperationRequest} that represents the queued
* operation.
*/
OperationRequest appendOperation(OperationType opType, Wavelet wavelet,
Parameter... parameters) {
return appendOperation(opType, wavelet.getWaveId(), wavelet.getWaveletId(), null, parameters);
}
/**
* Creates and appends a new operation to the operation queue.
*
* @param opType the type of the operation.
* @param blip the blip to apply this operation to.
* @param parameters the parameters that should be added as a property of
* the operation.
* @return an instance of {@link OperationRequest} that represents the queued
* operation.
*/
OperationRequest appendOperation(OperationType opType, Blip blip, Parameter... parameters) {
return appendOperation(opType, blip.getWaveId(), blip.getWaveletId(), blip.getBlipId(),
parameters);
}
/**
* Creates and appends a new operation to the operation queue.
*
* @param opType the type of the operation.
* @param waveId the wave id in which the operation should be applied to.
* @param waveletId the wavelet id of the given wave in which the operation
* should be applied to.
* @param blipId the optional blip id of the given wave in which the operation
* should be applied to. Not all operations require blip id.
* @param parameters the parameters that should be added as a property of
* the operation.
* @return an instance of {@link OperationRequest} that represents the queued
* operation.
*/
OperationRequest appendOperation(OperationType opType, WaveId waveId, WaveletId waveletId,
String blipId, Parameter... parameters) {
return addOperation(opType, waveId, waveletId, blipId, pendingOperations.size(), parameters);
}
/**
* Creates and prepends a new operation to the operation queue.
*
* @param opType the type of the operation.
* @param waveId the wave id in which the operation should be applied to.
* @param waveletId the wavelet id of the given wave in which the operation
* should be applied to.
* @param blipId the optional blip id of the given wave in which the operation
* should be applied to. Not all operations require blip id.
* @param parameters the parameters that should be added as a property of
* the operation.
* @return an instance of {@link OperationRequest} that represents the queued
* operation.
*/
OperationRequest prependOperation(OperationType opType, WaveId waveId, WaveletId waveletId,
String blipId, Parameter... parameters) {
return addOperation(opType, waveId, waveletId, blipId, 0, parameters);
}
/**
* Creates and adds a new operation to the operation queue.
*
* @param opType the type of the operation.
* @param waveId the wave id in which the operation should be applied to.
* @param waveletId the wavelet id of the given wave in which the operation
* should be applied to.
* @param blipId the optional blip id of the given wave in which the operation
* should be applied to. Not all operations require blip id.
* @param index the index where this new operation should be added to in the
* queue.
* @param parameters the parameters that should be added as a property of
* the operation.
* @return an instance of {@link OperationRequest} that represents the queued
* operation.
*/
OperationRequest addOperation(OperationType opType, WaveId waveId, WaveletId waveletId,
String blipId, int index, Parameter... parameters) {
String waveIdString = null;
if (waveId != null) {
waveIdString = ApiIdSerializer.instance().serialiseWaveId(waveId);
}
String waveletIdString = null;
if (waveletId != null) {
waveletIdString = ApiIdSerializer.instance().serialiseWaveletId(waveletId);
}
OperationRequest operation = new OperationRequest(opType.method(),
String.format(OP_ID_FORMAT, nextOpId++),
waveIdString, waveletIdString, blipId, parameters);
// Set the proxying for parameter, if necessary.
if (proxyForId != null && !proxyForId.isEmpty()) {
operation.addParameter(Parameter.of(ParamsProperty.PROXYING_FOR, proxyForId));
}
pendingOperations.add(index, operation);
return operation;
}
/**
* Generates a temporary blip id.
*
* @param wavelet the wavelet to seed the temporary id.
* @return a temporary blip id.
*/
private static String generateTempBlipId(Wavelet wavelet) {
return String.format(TEMP_BLIP_ID_FORMAT,
ApiIdSerializer.instance().serialiseWaveletId(wavelet.getWaveletId()),
ID_GENERATOR.nextInt());
}
/**
* Creates a new {@code Blip} object used for this session. A temporary
* id will be assigned to the newly created {@code Blip} object.
*
* @param wavelet the wavelet that owns this blip.
* @param initialContent the initial content of the new blip.
* @param parentBlipId the parent of this blip.
* @return an instance of new {@code Blip} object used for this session.
*/
private static Blip newBlip(Wavelet wavelet, String initialContent, String parentBlipId,
String blipId, String threadId) {
Blip newBlip = new Blip(blipId, initialContent, parentBlipId, threadId, wavelet);
if (parentBlipId != null) {
Blip parentBlip = wavelet.getBlips().get(parentBlipId);
if (parentBlip != null) {
parentBlip.getChildBlipIds().add(newBlip.getBlipId());
}
}
wavelet.getBlips().put(newBlip.getBlipId(), newBlip);
BlipThread thread = wavelet.getThread(threadId);
if (thread != null) {
thread.appendBlip(newBlip);
}
return newBlip;
}
/**
* Creates a new {@code Wavelet} object used for this session. A temporary
* wave id will be assigned to this newly created {@code Wavelet} object.
*
* @param domain the domain that is used for the wave and wavelet ids.
* @param participants the participants that should be added to the new
* wavelet.
* @param opQueue the operation queue of the new wavelet.
* @return an instance of new {@code Wavelet} object used for this
* session.
*/
private static Wavelet newWavelet(String domain, Set<String> participants,
OperationQueue opQueue) {
// Make sure that participant list is not null;
if (participants == null) {
participants = Collections.emptySet();
}
WaveId waveId;
WaveletId waveletId;
try {
waveId = ApiIdSerializer.instance().deserialiseWaveId(
String.format(TEMP_WAVE_ID_FORMAT, domain, ID_GENERATOR.nextInt()));
waveletId = ApiIdSerializer.instance().deserialiseWaveletId(
String.format(TEMP_WAVELET_ID_FORMAT, domain));
} catch (InvalidIdException e) {
throw new IllegalStateException("Invalid temporary id", e);
}
String rootBlipId = String.format(TEMP_BLIP_ID_FORMAT,
ApiIdSerializer.instance().serialiseWaveletId(waveletId),
ID_GENERATOR.nextInt());
Map<String, Blip> blips = new HashMap<String, Blip>();
Map<String, String> roles = new HashMap<String, String>();
Map<String, BlipThread> threads = new HashMap<String, BlipThread>();
List<String> blipIds = new ArrayList<String>();
blipIds.add(rootBlipId);
BlipThread rootThread = new BlipThread("", -1, blipIds, blips);
Wavelet wavelet = new Wavelet(waveId, waveletId, rootBlipId, rootThread, participants,
roles, blips, threads, opQueue);
Blip rootBlip = new Blip(rootBlipId, "", null, "", wavelet);
blips.put(rootBlipId, rootBlip);
return wavelet;
}
/**
* Modifies the role of a participant in a wavelet.
*
* @param wavelet the wavelet that the participant is on
* @param participant whose role to modify
* @param role to set for the participant
*/
public void modifyParticipantRoleOfWavelet(Wavelet wavelet, String participant, String role) {
appendOperation(OperationType.WAVELET_MODIFY_PARTICIPANT_ROLE, wavelet,
Parameter.of(ParamsProperty.PARTICIPANT_ID, participant),
Parameter.of(ParamsProperty.PARTICIPANT_ROLE, role));
}
/**
* Notifies the robot information.
*
* @param protocolVersion the wire protocol version of the robot.
* @param capabilitiesHash the capabilities hash of the robot.
*/
public void notifyRobotInformation(ProtocolVersion protocolVersion, String capabilitiesHash) {
prependOperation(OperationType.ROBOT_NOTIFY, null, null, null,
Parameter.of(ParamsProperty.PROTOCOL_VERSION, protocolVersion.getVersionString()),
Parameter.of(ParamsProperty.CAPABILITIES_HASH, capabilitiesHash));
}
public void fetchProfiles(FetchProfilesRequest request) {
appendOperation(OperationType.ROBOT_FETCH_PROFILES,
Parameter.of(ParamsProperty.FETCH_PROFILES_REQUEST, request));
}
}