http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/operations/SearchService.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/operations/SearchService.java b/src/org/waveprotocol/box/server/robots/operations/SearchService.java deleted file mode 100644 index e8eeedd..0000000 --- a/src/org/waveprotocol/box/server/robots/operations/SearchService.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * 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.operations; - -import com.google.common.collect.ImmutableMap; -import com.google.inject.Inject; -import com.google.wave.api.InvalidRequestException; -import com.google.wave.api.JsonRpcConstant.ParamsProperty; -import com.google.wave.api.OperationRequest; -import com.google.wave.api.SearchResult; -import org.waveprotocol.box.server.robots.OperationContext; -import org.waveprotocol.box.server.robots.util.OperationUtil; -import org.waveprotocol.box.server.waveserver.SearchProvider; -import org.waveprotocol.wave.model.wave.ParticipantId; -import java.util.Map; - -/** - * {@link OperationService} for the "search" operation. - * - * @author [email protected] (Lennard de Rijk) - * @author [email protected] (Joseph Gentle) - */ -public class SearchService implements OperationService { - - /** - * The number of search results to return if not defined in the request. - * Defined in the spec. - */ - private static final int DEFAULT_NUMBER_SEARCH_RESULTS = 10; - - private final SearchProvider searchProvider; - - @Inject - public SearchService(SearchProvider searchProvider) { - this.searchProvider = searchProvider; - } - - @Override - public void execute( - OperationRequest operation, OperationContext context, ParticipantId participant) - throws InvalidRequestException { - String query = OperationUtil.getRequiredParameter(operation, ParamsProperty.QUERY); - int index = OperationUtil.getOptionalParameter(operation, ParamsProperty.INDEX, 0); - int numResults = OperationUtil.getOptionalParameter( - operation, ParamsProperty.NUM_RESULTS, DEFAULT_NUMBER_SEARCH_RESULTS); - - SearchResult result = search(participant, query, index, numResults); - - Map<ParamsProperty, Object> data = - ImmutableMap.<ParamsProperty, Object> of(ParamsProperty.SEARCH_RESULTS, result); - context.constructResponse(operation, data); - } - - // Note that this search implementation is only of prototype quality. - private SearchResult search( - ParticipantId participant, String query, int startAt, int numResults) { - return searchProvider.search(participant, query, startAt, numResults); - } -}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/operations/WaveletSetTitleService.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/operations/WaveletSetTitleService.java b/src/org/waveprotocol/box/server/robots/operations/WaveletSetTitleService.java deleted file mode 100644 index ee52fc9..0000000 --- a/src/org/waveprotocol/box/server/robots/operations/WaveletSetTitleService.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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.operations; - -import com.google.wave.api.InvalidRequestException; -import com.google.wave.api.JsonRpcConstant.ParamsProperty; -import com.google.wave.api.OperationRequest; - -import org.waveprotocol.box.server.robots.OperationContext; -import org.waveprotocol.box.server.robots.util.OperationUtil; -import org.waveprotocol.wave.model.conversation.ObservableConversation; -import org.waveprotocol.wave.model.conversation.TitleHelper; -import org.waveprotocol.wave.model.document.Document; -import org.waveprotocol.wave.model.wave.ParticipantId; - -/** - * Implements the "wavelet.setTitle" operation. - * - * @author [email protected] (Yuri Zelikov) - */ -public class WaveletSetTitleService implements OperationService { - - @Override - public void execute( - OperationRequest operation, OperationContext context, ParticipantId participant) - throws InvalidRequestException { - - String title = - OperationUtil.getRequiredParameter(operation, ParamsProperty.WAVELET_TITLE); - ObservableConversation conversation = - context.openConversation(operation, participant).getRoot(); - String blipId = conversation.getRootThread().getFirstBlip().getId(); - Document doc = context.getBlip(conversation, blipId).getContent(); - TitleHelper.setExplicitTitle(doc, title); - } - - public static WaveletSetTitleService create() { - return new WaveletSetTitleService(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/EventGenerator.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/EventGenerator.java b/src/org/waveprotocol/box/server/robots/passive/EventGenerator.java deleted file mode 100644 index c45b73e..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/EventGenerator.java +++ /dev/null @@ -1,626 +0,0 @@ -/** - * 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.passive; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.wave.api.BlipData; -import com.google.wave.api.Context; -import com.google.wave.api.Gadget; -import com.google.wave.api.data.converter.ContextResolver; -import com.google.wave.api.data.converter.EventDataConverter; -import com.google.wave.api.event.*; -import com.google.wave.api.impl.EventMessageBundle; -import com.google.wave.api.robot.Capability; -import com.google.wave.api.robot.RobotName; -import org.waveprotocol.box.server.robots.util.ConversationUtil; -import org.waveprotocol.box.server.util.WaveletDataUtil; -import org.waveprotocol.wave.model.conversation.*; -import org.waveprotocol.wave.model.document.Doc.E; -import org.waveprotocol.wave.model.document.Doc.N; -import org.waveprotocol.wave.model.document.Doc.T; -import org.waveprotocol.wave.model.document.DocHandler; -import org.waveprotocol.wave.model.document.ObservableDocument; -import org.waveprotocol.wave.model.document.indexed.DocumentEvent; -import org.waveprotocol.wave.model.document.indexed.DocumentEvent.AnnotationChanged; -import org.waveprotocol.wave.model.document.indexed.DocumentEvent.AttributesModified; -import org.waveprotocol.wave.model.document.indexed.DocumentEvent.ContentInserted; -import org.waveprotocol.wave.model.document.raw.impl.Node; -import org.waveprotocol.wave.model.operation.OperationException; -import org.waveprotocol.wave.model.operation.SilentOperationSink; -import org.waveprotocol.wave.model.operation.wave.BasicWaveletOperationContextFactory; -import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; -import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; -import org.waveprotocol.wave.model.operation.wave.WaveletOperation; -import org.waveprotocol.wave.model.wave.*; -import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; -import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; -import org.waveprotocol.wave.model.wave.opbased.WaveletListenerImpl; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Generates Robot API Events from operations applied to a Wavelet. - * - * <p> - * Events that exist in the API: - * <li>WaveletBlipCreated (DONE)</li> - * <li>WaveletBlipRemoved (DONE)</li> - * <li>WaveletParticipantsChanged (DONE)</li> - * <li>WaveletSelfAdded (DONE)</li> - * <li>WaveletSelfRemoved (DONE)</li> - * <li>DocumentChanged (DONE)</li> - * <li>AnnotatedTextChanged (DONE)</li> - * <li>FormButtonClicked (TBD)</li> - * <li>GadgetStateChanged (DONE)</li> - * <li>BlipContributorChanged (TBD)</li> - * <li>WaveletTagsChanged (TBD)</li> - * <li>WaveletTitleChanged (TBD)</li> - * <li>BlipSubmitted (Will not be supported, submit ops will be phased out)</li> - * - * @author [email protected] (Lennard de Rijk) - */ -public class EventGenerator { - - private static class EventGeneratingWaveletListener extends WaveletListenerImpl { - @SuppressWarnings("unused") - private final Map<EventType, Capability> capabilities; - - /** - * Creates a {@link WaveletListener} which will generate events according to - * the capabilities. - * - * @param capabilities the capabilities which we are interested in. - */ - public EventGeneratingWaveletListener(Map<EventType, Capability> capabilities) { - this.capabilities = capabilities; - } - // TODO(ljvderijk): implement more events. This class should listen for - // non-conversational blip changes and robot data documents as indicated by - // IdConstants.ROBOT_PREFIX - } - - private class EventGeneratingConversationListener extends ConversationListenerImpl { - private final Map<EventType, Capability> capabilities; - private final Conversation conversation; - private final EventMessageBundle messages; - - // Event collectors - private final List<String> participantsAdded = Lists.newArrayList(); - private final List<String> participantsRemoved = Lists.newArrayList(); - - // Changes for each delta - private ParticipantId deltaAuthor; - private Long deltaTimestamp; - - /** - * Creates a {@link ObservableConversation.Listener} which will generate - * events according to the capabilities. - * - * @param conversation the conversation we are observing. - * @param capabilities the capabilities which we are interested in. - * @param messages the bundle to put the events in. - */ - public EventGeneratingConversationListener(Conversation conversation, - Map<EventType, Capability> capabilities, EventMessageBundle messages, RobotName robotName) { - this.conversation = conversation; - this.capabilities = capabilities; - this.messages = messages; - } - - /** - * Prepares this listener for events coming from a single delta. - * - * @param author the author of the delta. - * @param timestamp the timestamp of the delta. - */ - public void deltaBegin(ParticipantId author, long timestamp) { - Preconditions.checkState( - deltaAuthor == null && deltaTimestamp == null, "DeltaEnd wasn't called"); - Preconditions.checkNotNull(author, "Author should not be null"); - Preconditions.checkNotNull(timestamp, "Timestamp should not be null"); - - deltaAuthor = author; - deltaTimestamp = timestamp; - } - - @Override - public void onParticipantAdded(ParticipantId participant) { - if (capabilities.containsKey(EventType.WAVELET_PARTICIPANTS_CHANGED)) { - boolean removedBefore = participantsRemoved.remove(participant.getAddress()); - if (!removedBefore) { - participantsAdded.add(participant.getAddress()); - } - } - - // This deviates from Google Wave production which always sends this - // event, even if it wasn't present in your capabilities. - if (capabilities.containsKey(EventType.WAVELET_SELF_ADDED) && participant.equals(robotId)) { - // The robot has been added - String rootBlipId = ConversationUtil.getRootBlipId(conversation); - WaveletSelfAddedEvent event = new WaveletSelfAddedEvent( - null, null, deltaAuthor.getAddress(), deltaTimestamp, rootBlipId); - addEvent(event, capabilities, rootBlipId, messages); - } - } - - @Override - public void onParticipantRemoved(ParticipantId participant) { - if (capabilities.containsKey(EventType.WAVELET_PARTICIPANTS_CHANGED)) { - participantsRemoved.add(participant.getAddress()); - } - - if (capabilities.containsKey(EventType.WAVELET_SELF_REMOVED) && participant.equals(robotId)) { - String rootBlipId = ConversationUtil.getRootBlipId(conversation); - WaveletSelfRemovedEvent event = new WaveletSelfRemovedEvent( - null, null, deltaAuthor.getAddress(), deltaTimestamp, rootBlipId); - addEvent(event, capabilities, rootBlipId, messages); - } - } - - @Override - public void onBlipAdded(ObservableConversationBlip blip) { - if (capabilities.containsKey(EventType.WAVELET_BLIP_CREATED)) { - String rootBlipId = ConversationUtil.getRootBlipId(conversation); - WaveletBlipCreatedEvent event = new WaveletBlipCreatedEvent( - null, null, deltaAuthor.getAddress(), deltaTimestamp, rootBlipId, blip.getId()); - addEvent(event, capabilities, rootBlipId, messages); - } - } - - @Override - public void onBlipDeleted(ObservableConversationBlip blip) { - if (capabilities.containsKey(EventType.WAVELET_BLIP_REMOVED)) { - String rootBlipId = ConversationUtil.getRootBlipId(conversation); - WaveletBlipRemovedEvent event = new WaveletBlipRemovedEvent( - null, null, deltaAuthor.getAddress(), deltaTimestamp, rootBlipId, blip.getId()); - addEvent(event, capabilities, rootBlipId, messages); - } - } - - /** - * Generates the events that are collected over the span of one delta. - */ - public void deltaEnd() { - if (!participantsAdded.isEmpty() || !participantsRemoved.isEmpty()) { - String rootBlipId = ConversationUtil.getRootBlipId(conversation); - - WaveletParticipantsChangedEvent event = - new WaveletParticipantsChangedEvent(null, null, deltaAuthor.getAddress(), - deltaTimestamp, rootBlipId, participantsAdded, participantsRemoved); - addEvent(event, capabilities, rootBlipId, messages); - } - clearOncePerDeltaCollectors(); - - deltaAuthor = null; - deltaTimestamp = null; - } - - /** - * Clear the data structures responsible for collecting data for events that - * should only be fired once per delta. - */ - private void clearOncePerDeltaCollectors() { - participantsAdded.clear(); - participantsRemoved.clear(); - } - } - - private class EventGeneratingDocumentHandler implements DocHandler { - - /** Public so we can manage the subscription */ - public final ObservableDocument doc; - - private final ConversationBlip blip; - private final Map<EventType, Capability> capabilities; - private final EventMessageBundle messages; - private ParticipantId deltaAuthor; - private Long deltaTimestamp; - - /** - * Set to true if a {@link DocumentChangedEvent} has been generated by this - * handler. - */ - private boolean documentChangedEventGenerated; - - private EventDataConverter converter; - private Wavelet wavelet; - - public EventGeneratingDocumentHandler(ObservableDocument doc, ConversationBlip blip, - Map<EventType, Capability> capabilities, EventMessageBundle messages, - ParticipantId deltaAuthor, Long deltaTimestamp, Wavelet wavelet, - EventDataConverter converter) { - this.doc = doc; - this.blip = blip; - this.capabilities = capabilities; - this.messages = messages; - this.converter = converter; - this.wavelet = wavelet; - setAuthorAndTimeStamp(deltaAuthor, deltaTimestamp); - } - - @Override - public void onDocumentEvents(EventBundle<N, E, T> event) { - Iterable<DocumentEvent<N, E, T>> eventComponents = event.getEventComponents(); - - for (DocumentEvent<N, E, T> eventComponent : eventComponents) { - if (eventComponent.getType() == DocumentEvent.Type.ANNOTATION_CHANGED) { - if (capabilities.containsKey(EventType.ANNOTATED_TEXT_CHANGED)) { - AnnotationChanged<N, E, T> anotationChangedEvent = - (AnnotationChanged<N, E, T>) eventComponent; - AnnotatedTextChangedEvent apiEvent = - new AnnotatedTextChangedEvent(null, null, deltaAuthor.getAddress(), deltaTimestamp, - blip.getId(), anotationChangedEvent.key, anotationChangedEvent.newValue); - addEvent(apiEvent, capabilities, blip.getId(), messages); - } - } else { - // used to distinguish between attribute changes and gadget state - // changes - Boolean gadgetStateChangeEvent = false; - if (eventComponent.getType() == DocumentEvent.Type.ATTRIBUTES) { - if (capabilities.containsKey(EventType.GADGET_STATE_CHANGED)) { - Map<String, String> oldState = new HashMap<>(); - Integer index = -1; - try { - AttributesModified<N, E, T> attributesModified = - (AttributesModified<N, E, T>) eventComponent; - // When a gadget state changes, the AttributesModifies event has - // always - // an oldValue map of the form {"value", something} (key is - // always value). - // To obtain the key of the changed state, the attribute "name" - // has to be obtained - // from the Element of the AttributesModified event. - String name = - ((org.waveprotocol.wave.model.document.raw.impl.Element) attributesModified - .getElement()).getAttribute("name"); - String oldValue = attributesModified.getOldValues().get("value"); - if (name != null || oldValue != null) { - oldState.put(name, oldValue); - } - BlipData b = converter.toBlipData(blip, wavelet, messages); - Map<Integer, com.google.wave.api.Element> elements = b.getElements(); - Set<Integer> keys = elements.keySet(); - // The gadget element provided by the eventComponent - org.waveprotocol.wave.model.document.raw.impl.Element rawGadget = - ((Node) attributesModified.getElement()).getParentElement(); - for (Integer key : keys) { - try { - Gadget gadget = (Gadget) elements.get(key); - if (sameGadgets(rawGadget, gadget)) { - index = key; - break; - } - } catch (ClassCastException e) { - // if it is not a gadget we do not compare them - } - } - } catch (ClassCastException e) { - e.printStackTrace(); - } - if (oldState.size() != 0 && index != -1) { - // if the attribute changed belongs to a gadget - gadgetStateChangeEvent = true; - final GadgetStateChangedEvent gadgetEvent = - new GadgetStateChangedEvent(null, messages, deltaAuthor.getAddress(), - deltaTimestamp, blip.getId(), index, oldState); - addEvent(gadgetEvent, capabilities, blip.getId(), messages); - } - } - } - if (capabilities.containsKey(EventType.FORM_BUTTON_CLICKED)) { - if (eventComponent.getType() == DocumentEvent.Type.CONTENT_INSERTED) { - ContentInserted<N, E, T> contentInserted = (ContentInserted<N, E, T>) eventComponent; - org.waveprotocol.wave.model.document.raw.impl.Element elementInserted = - ((org.waveprotocol.wave.model.document.raw.impl.Element) - contentInserted.getSubtreeElement()); - if (elementInserted.getTagName().equals("click")) { - FormButtonClickedEvent buttonClickedEvent = - new FormButtonClickedEvent(null, null, - elementInserted.getAttribute("clicker"), Long.decode(elementInserted - .getAttribute("time")), blip.getId(), elementInserted - .getParentElement().getAttribute("name")); - addEvent(buttonClickedEvent, capabilities, blip.getId(), messages); - } - } - } - if (capabilities.containsKey(EventType.DOCUMENT_CHANGED) - && !documentChangedEventGenerated && !gadgetStateChangeEvent) { - DocumentChangedEvent apiEvent = - new DocumentChangedEvent(null, null, deltaAuthor.getAddress(), deltaTimestamp, - blip.getId()); - addEvent(apiEvent, capabilities, blip.getId(), messages); - // Only one documentChangedEvent should be generated per bundle. - documentChangedEventGenerated = true; - } - } - } - } - - /** - * Sets the author and timestamp for the events that will be coming in. - * Should be changed at least for every delta that will touch the document - * that the handler is listening to. - * - * @param author the author of the delta. - * @param timestamp the timestamp at which the delta is applied. - */ - public void setAuthorAndTimeStamp(ParticipantId author, long timestamp) { - Preconditions.checkNotNull(author, "Author should not be null"); - Preconditions.checkNotNull(timestamp, "Timestamp should not be null"); - this.deltaAuthor = author; - this.deltaTimestamp = timestamp; - } - - /** - * Check if an {@link org.waveprotocol.wave.model.document.raw.impl.Element} - * is and a {@link Gadget} - * - * @param rawElement - * @param element - * @return - */ - private boolean sameGadgets(org.waveprotocol.wave.model.document.raw.impl.Element rawElement, - Gadget element) { - String ifr1 = rawElement.getAttribute("ifr"); - String ifr2 = element.getProperty("ifr"); - return (ifr1 != null && ifr1.equals(ifr2)); - } - } - - /** - * Adds an {@link Event} to the given {@link EventMessageBundle}. - * - * If a blip id is specified this will be added to the - * {@link EventMessageBundle}'s required blips list with the context given by - * the robot's capabilities. If a robot does not specify a context for this - * event the default context will be used. Ergo this code is not responsible - * for filtering operations that a robot is not interested in. - * - * @param event to add. - * @param capabilities the capabilities to get the context from. - * @param blipId id of the blip this event is related to, may be null. - * @param messages {@link EventMessageBundle} to edit. - */ - private void addEvent(Event event, Map<EventType, Capability> capabilities, String blipId, - EventMessageBundle messages) { - if (!isEventFilteredOut(event)) { - // Add the given blip to the required blip lists with the context - // specified by the robot's capabilities. - if (!Strings.isNullOrEmpty(blipId)) { - Capability capability = capabilities.get(event.getType()); - List<Context> contexts; - if (capability == null) { - contexts = Capability.DEFAULT_CONTEXT; - } else { - contexts = capability.getContexts(); - } - messages.requireBlip(blipId, contexts); - } - // Add the event to the bundle. - messages.addEvent(event); - } - } - - /** - * Checks whether the event should be filtered out. It can happen - * if the robot received several deltas where in some delta it is added to - * the wavelet but it didn't receive the WAVELET_SELF_ADDED event yet. - * Or if robot already received WAVELET_SELF_REMOVED - * event - then it should not receive events after that. - * - * @param event the event to filter. - * @return true if the event should be filtered out - */ - protected boolean isEventFilteredOut(Event event) { - boolean isEventSuspensionOveriden = false; - if (event.getType().equals(EventType.WAVELET_SELF_REMOVED)) { - // Stop processing events. - isEventProcessingSuspended = true; - // Allow robot receive WAVELET_SELF_REMOVED event, but suspend after that. - isEventSuspensionOveriden = true; - } - if (event.getType().equals(EventType.WAVELET_SELF_ADDED)) { - // Start processing events. - isEventProcessingSuspended = false; - } - if ((isEventProcessingSuspended && !isEventSuspensionOveriden) - || event.getModifiedBy().equals(robotName.toParticipantAddress())) { - // Robot was removed from wave or this is self generated event. - return true; - } - return false; - } - - /** - * The name of the Robot to which this {@link EventGenerator} belongs. Used - * for events where "self" is important. - */ - private final RobotName robotName; - - /** Used to create conversations. */ - private final ConversationUtil conversationUtil; - - /** - * Indicates that robot was removed from wavelet and thus event processing - * should be suspended. - */ - private boolean isEventProcessingSuspended; - - private final ParticipantId robotId; - - /** - * Constructs a new {@link EventGenerator} for the robot with the given name. - * - * @param robotName the name of the robot. - * @param conversationUtil used to create conversations. - */ - public EventGenerator(RobotName robotName, ConversationUtil conversationUtil) { - this.robotName = robotName; - this.conversationUtil = conversationUtil; - this.robotId = ParticipantId.ofUnsafe(robotName.toParticipantAddress()); - } - - /** - * Generates the {@link EventMessageBundle} for the specified capabilities. - * - * @param waveletAndDeltas for which the events are to be generated - * @param capabilities the capabilities to filter events on - * @param converter converter for generating the API implementations of - * WaveletData and BlipData. - * @returns true if an event was generated, false otherwise - */ - public EventMessageBundle generateEvents(WaveletAndDeltas waveletAndDeltas, - Map<EventType, Capability> capabilities, EventDataConverter converter) { - EventMessageBundle messages = new EventMessageBundle(robotName.toEmailAddress(), ""); - ObservableWaveletData snapshot = - WaveletDataUtil.copyWavelet(waveletAndDeltas.getSnapshotBeforeDeltas()); - isEventProcessingSuspended = !snapshot.getParticipants().contains(robotId); - - if (robotName.hasProxyFor()) { - // This robot is proxying so set the proxy field. - messages.setProxyingFor(robotName.getProxyFor()); - } - - // Sending any operations will cause an exception. - OpBasedWavelet wavelet = - new OpBasedWavelet(snapshot.getWaveId(), snapshot, - // This doesn't thrown an exception, the sinks will - new BasicWaveletOperationContextFactory(null), - ParticipationHelper.DEFAULT, SilentOperationSink.VOID, SilentOperationSink.VOID); - - ObservableConversation conversation = getRootConversation(wavelet); - - if (conversation == null) { - return messages; - } - - // Start listening - EventGeneratingConversationListener conversationListener = - new EventGeneratingConversationListener(conversation, capabilities, messages, robotName); - conversation.addListener(conversationListener); - EventGeneratingWaveletListener waveletListener = - new EventGeneratingWaveletListener(capabilities); - wavelet.addListener(waveletListener); - - Map<String, EventGeneratingDocumentHandler> docHandlers = Maps.newHashMap(); - try { - for (TransformedWaveletDelta delta : waveletAndDeltas.getDeltas()) { - // TODO(ljvderijk): Set correct timestamp and hashed version once - // wavebus sends them along - long timestamp = 0L; - conversationListener.deltaBegin(delta.getAuthor(), timestamp); - - for (WaveletOperation op : delta) { - // Check if we need to attach a doc handler. - if ((op instanceof WaveletBlipOperation)) { - attachDocHandler(conversation, op, docHandlers, capabilities, messages, - delta.getAuthor(), timestamp, wavelet, converter); - } - op.apply(snapshot); - } - conversationListener.deltaEnd(); - } - } catch (OperationException e) { - throw new IllegalStateException("Operation failed to apply when generating events", e); - } finally { - conversation.removeListener(conversationListener); - wavelet.removeListener(waveletListener); - for (EventGeneratingDocumentHandler docHandler : docHandlers.values()) { - docHandler.doc.removeListener(docHandler); - } - } - - if (messages.getEvents().isEmpty()) { - // No events found, no need to resolve contexts - return messages; - } - - // Resolve the context of the bundle now that all events have been - // processed. - ContextResolver.resolveContext(messages, wavelet, conversation, converter); - - return messages; - } - - /** - * Attaches a doc handler to the blip the operation applies to. - * - * @param conversation the conversation the op is to be applied to. - * @param op the op to be applied - * @param docHandlers the list of attached dochandlers. - * @param capabilities the capabilities of the robot. - * @param messages the bundle to put the generated events in. - * @param deltaAuthor the author of the events generated. - * @param timestamp the timestamp at which these events occurred. - */ - private void attachDocHandler(ObservableConversation conversation, WaveletOperation op, - Map<String, EventGeneratingDocumentHandler> docHandlers, - Map<EventType, Capability> capabilities, EventMessageBundle messages, - ParticipantId deltaAuthor, long timestamp, Wavelet wavelet, EventDataConverter converter) { - WaveletBlipOperation blipOp = (WaveletBlipOperation) op; - String blipId = blipOp.getBlipId(); - // Ignoring the documents outside the conversation such as tags - // and robot data docs. - ObservableConversationBlip blip = conversation.getBlip(blipId); - if (blip != null) { - String blipId1 = blip.getId(); - - EventGeneratingDocumentHandler docHandler = docHandlers.get(blipId1); - if (docHandler == null) { - ObservableDocument doc = (ObservableDocument) blip.getContent(); - docHandler = - new EventGeneratingDocumentHandler(doc, blip, capabilities, messages, deltaAuthor, - timestamp, wavelet, converter); - doc.addListener(docHandler); - docHandlers.put(blipId1, docHandler); - } else { - docHandler.setAuthorAndTimeStamp(deltaAuthor, timestamp); - } - } - } - - /** - * Returns the root conversation from the given wavelet. Or null if there is - * none. - * - * @param wavelet the wavelet to get the conversation from. - */ - private ObservableConversation getRootConversation(ObservableWavelet wavelet) { - if (!WaveletBasedConversation.waveletHasConversation(wavelet)) { - // No conversation present, bail. - return null; - } - - ObservableConversation conversation = conversationUtil.buildConversation(wavelet).getRoot(); - if (conversation.getRootThread().getFirstBlip() == null) { - // No root blip is present, this will cause Robot API code - // to fail when resolving the context of events. This might be fixed later - // on by making changes to the ContextResolver. - return null; - } - return conversation; - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/OperationServiceRegistryImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/OperationServiceRegistryImpl.java b/src/org/waveprotocol/box/server/robots/passive/OperationServiceRegistryImpl.java deleted file mode 100644 index 89f7d98..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/OperationServiceRegistryImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * 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.passive; - -import com.google.inject.Inject; -import com.google.wave.api.OperationType; - -import org.waveprotocol.box.server.robots.AbstractOperationServiceRegistry; -import org.waveprotocol.box.server.robots.operations.BlipOperationServices; -import org.waveprotocol.box.server.robots.operations.CreateWaveletService; -import org.waveprotocol.box.server.robots.operations.DocumentModifyService; -import org.waveprotocol.box.server.robots.operations.FolderActionService; -import org.waveprotocol.box.server.robots.operations.NotifyOperationService; -import org.waveprotocol.box.server.robots.operations.OperationService; -import org.waveprotocol.box.server.robots.operations.ParticipantServices; -import org.waveprotocol.box.server.robots.operations.WaveletSetTitleService; - -/** - * Class for registering and accessing {@link OperationService} to execute - * operations for use in the Passive Robot API. - * - * @author [email protected] (Lennard de Rijk) - */ -public final class OperationServiceRegistryImpl extends AbstractOperationServiceRegistry { - - // Suppressing warnings about operations that are deprecated but still used by - // the default client libraries. - @SuppressWarnings("deprecation") - @Inject - OperationServiceRegistryImpl(NotifyOperationService notifyOpService) { - super(); - - // Register all the OperationProviders - register(OperationType.ROBOT_NOTIFY, notifyOpService); - register(OperationType.ROBOT_NOTIFY_CAPABILITIES_HASH, notifyOpService); - register(OperationType.WAVELET_ADD_PARTICIPANT_NEWSYNTAX, ParticipantServices.create()); - register(OperationType.WAVELET_APPEND_BLIP, BlipOperationServices.create()); - register(OperationType.WAVELET_REMOVE_PARTICIPANT_NEWSYNTAX, ParticipantServices.create()); - register(OperationType.BLIP_CONTINUE_THREAD, BlipOperationServices.create()); - register(OperationType.BLIP_CREATE_CHILD, BlipOperationServices.create()); - register(OperationType.BLIP_DELETE, BlipOperationServices.create()); - register(OperationType.DOCUMENT_APPEND_INLINE_BLIP, BlipOperationServices.create()); - register(OperationType.DOCUMENT_APPEND_MARKUP, BlipOperationServices.create()); - register(OperationType.DOCUMENT_INSERT_INLINE_BLIP, BlipOperationServices.create()); - register(OperationType.DOCUMENT_INSERT_INLINE_BLIP_AFTER_ELEMENT, - BlipOperationServices.create()); - register(OperationType.WAVELET_CREATE, CreateWaveletService.create()); - register(OperationType.DOCUMENT_MODIFY, DocumentModifyService.create()); - register(OperationType.WAVELET_SET_TITLE, WaveletSetTitleService.create()); - register(OperationType.ROBOT_FOLDER_ACTION, FolderActionService.create()); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/Robot.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/Robot.java b/src/org/waveprotocol/box/server/robots/passive/Robot.java deleted file mode 100644 index 7098512..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/Robot.java +++ /dev/null @@ -1,284 +0,0 @@ -/** - * 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.passive; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.ListMultimap; -import com.google.wave.api.OperationRequest; -import com.google.wave.api.data.converter.EventDataConverterManager; -import com.google.wave.api.impl.EventMessageBundle; -import com.google.wave.api.robot.CapabilityFetchException; -import com.google.wave.api.robot.RobotName; - -import org.waveprotocol.box.common.DeltaSequence; -import org.waveprotocol.box.server.account.RobotAccountData; -import org.waveprotocol.box.server.persistence.PersistenceException; -import org.waveprotocol.box.server.robots.RobotCapabilities; -import org.waveprotocol.box.server.util.WaveletDataUtil; -import org.waveprotocol.box.server.waveserver.WaveletProvider; -import org.waveprotocol.wave.model.id.WaveletName; -import org.waveprotocol.wave.model.operation.OperationException; -import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; -import org.waveprotocol.wave.util.logging.Log; - -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; - -/** - * Represents a Robot in the passive API. Is responsible for providing a filter - * for events it is interested in. Is also able of sending events to robots and - * executing the operations it receives. It submits the delta back to the - * {@link RobotsGateway}. - * - * @author [email protected] (Lennard de Rijk) - */ -public class Robot implements Runnable { - - private static final Log LOG = Log.get(Robot.class); - /** Appended to the robot url which forms the endpoint for sending rpc calls */ - public static final String RPC_URL = "/_wave/robot/jsonrpc"; - /** - * Appended to the robot url which forms the endpoint for getting the - * capabilities - */ - public static final String CAPABILITIES_URL = "/_wave/capabilities.xml"; - - private final RobotName robotName; - // This is not final because it needs to be updated when the capabilities - // change. - // TODO(ljvderijk): Keep up to date with other updates to account? - private RobotAccountData account; - private final RobotsGateway gateway; - private final RobotConnector connector; - private final EventDataConverterManager converterManager; - /** - * This map acts as a queue of wavelets to process events for. The methods - * that add, updated and remove wavelets synchronize on this object to ensure - * consistency in face of concurrent changes. - */ - private final ListMultimap<WaveletName, WaveletAndDeltas> waveletAndDeltasMap = - LinkedListMultimap.<WaveletName, WaveletAndDeltas> create(); - private final EventGenerator eventGenerator; - private final RobotOperationApplicator operationApplicator; - - /** - * Constructs a new Robot which is characterized by its {@link RobotName}. - * - * @param robotName the name of the {@link Robot}. - * @param account the {@link RobotAccountData} belonging to this - * {@link Robot}. - * @param gateway the gateway this robot belongs to. - * @param connector the {@link RobotConnector} to make connections to - * {@link Robot}s. - * @param converterManager used to convert to Robot API objects. - * @param waveletProvider used to access wavelets and submit deltas. - * @param eventGenerator used to generate events - * @param operationApplicator used to apply the robot operations returned by a - * robot. - */ - Robot(RobotName robotName, RobotAccountData account, RobotsGateway gateway, - RobotConnector connector, EventDataConverterManager converterManager, - WaveletProvider waveletProvider, EventGenerator eventGenerator, - RobotOperationApplicator operationApplicator) { - Preconditions.checkArgument(account.isVerified(), "Account must be verified"); - this.robotName = robotName; - this.gateway = gateway; - this.connector = connector; - this.converterManager = converterManager; - this.eventGenerator = eventGenerator; - this.operationApplicator = operationApplicator; - - setAccount(account); - } - - /** - * Sets the account for this Robot. The address of the account must match the - * address in the {@link RobotName}. - * - * @param account the account to set. - */ - void setAccount(RobotAccountData account) { - Preconditions.checkArgument(robotName.toEmailAddress().equals(account.getId().getAddress()), - String.format("The given RobotAccountData doesn't match the RobotName. %s != %s", - account.getId(), robotName.toEmailAddress())); - this.account = account; - } - - /** - * Returns the name of this robot. - */ - RobotName getRobotName() { - return robotName; - } - - /** - * The {@link RobotAccountData} of this robot. - */ - RobotAccountData getAccount() { - return account; - } - - /** - * Processes a wavelet update for this {@link Robot}. - * - * <p> - * The robot keeps an internal queue of wavelets that are to be processed when - * run() is called. A new entry in the queue is made on two occasions. First - * if the robot has nothing enqueued for the given wavelet, secondly if the - * robot does have an update enqueued for the given wavelet but the deltas - * that are given are not contiguous with the current data. - * - * <p> - * This method synchronizes on the queue because we might be appending deltas - * while dequeueWavelet() is being called. - * - * @param wavelet the wavelet this update is taking place on. - * @param deltas the deltas that have been applied to the given wavelet. - * @throws OperationException if an update for a new wavelet could not be - * processed. - */ - void waveletUpdate(ReadableWaveletData wavelet, DeltaSequence deltas) - throws OperationException { - WaveletName waveletName = WaveletDataUtil.waveletNameOf(wavelet); - - synchronized (waveletAndDeltasMap) { - // This returns a view which we can append new delta collections to. - List<WaveletAndDeltas> wavelets = waveletAndDeltasMap.get(waveletName); - - if (wavelets.isEmpty()) { - WaveletAndDeltas waveletAndDeltas = WaveletAndDeltas.create(wavelet, deltas); - wavelets.add(waveletAndDeltas); - } else { - WaveletAndDeltas waveletAndDeltas = wavelets.get(wavelets.size() - 1); - if (waveletAndDeltas.areContiguousToCurrentVersion(deltas)) { - waveletAndDeltas.appendDeltas(wavelet, deltas); - } else { - // We are missing deltas, create a new collection. - waveletAndDeltas = WaveletAndDeltas.create(wavelet, deltas); - wavelets.add(waveletAndDeltas); - } - } - } - } - - /** - * Dequeues a wavelet for this {@link Robot}. - * - * <p> - * This method synchronizes on the queue because deltas might be added in - * waveletUpdate(). - * - * @return the next {@link WaveletAndDeltas} in the queue, null if there is - * none. - */ - @VisibleForTesting - WaveletAndDeltas dequeueWavelet() { - synchronized (waveletAndDeltasMap) { - Iterator<Entry<WaveletName, WaveletAndDeltas>> iterator = - waveletAndDeltasMap.entries().iterator(); - if (!iterator.hasNext()) { - return null; - } - WaveletAndDeltas wavelet = iterator.next().getValue(); - iterator.remove(); - return wavelet; - } - } - - /** - * Runs this {@link Robot} by checking its queue for a new wavelet and then - * processing this wavelet. In the end the {@link Robot} will check whether it - * needs to requeue itself in the RobotGateway or that no further actions need - * to be taken. - */ - @Override - public void run() { - try { - LOG.fine(robotName + " called for processing"); - - WaveletAndDeltas wavelet = dequeueWavelet(); - if (wavelet == null) { - gateway.doneRunning(this); - return; - } - process(wavelet); - } catch (RuntimeException e) { - LOG.severe("Unexpected error occurred when robot " + robotName + " was called", e); - } - - // Requeue since we either had an exception or we processed a wavelet. - gateway.doneRunning(this); - gateway.ensureScheduled(this); - } - - /** - * Processes a single {@link WaveletAndDeltas} by generating events that a - * {@link Robot} is subscribed to. These events are then sent off to the robot - * using the {@link RobotConnector} passed during construction. The operations - * returned by the robot are then processed by the - * {@link RobotOperationApplicator}. - * - * @param wavelet the {@link WaveletAndDeltas} to process. - */ - private void process(WaveletAndDeltas wavelet) { - if (account.getCapabilities() == null) { - try { - LOG.info(robotName + ": Initializing capabilities"); - gateway.updateRobotAccount(this); - } catch (CapabilityFetchException e) { - ReadableWaveletData snapshot = wavelet.getSnapshotAfterDeltas(); - LOG.info( - "Couldn't initialize the capabilities of robot(" + robotName - + "), dropping its wavelet(" + WaveletDataUtil.waveletNameOf(snapshot) - + ") at version " + wavelet.getVersionAfterDeltas(), e); - return; - } catch (PersistenceException e) { - ReadableWaveletData snapshot = wavelet.getSnapshotAfterDeltas(); - LOG.info( - "Couldn't initialize the capabilities of robot(" + robotName - + "), dropping its wavelet(" + WaveletDataUtil.waveletNameOf(snapshot) - + ") at version " + wavelet.getVersionAfterDeltas(), e); - return; - } - } - - RobotCapabilities capabilities = account.getCapabilities(); - EventMessageBundle messages = - eventGenerator.generateEvents(wavelet, capabilities.getCapabilitiesMap(), - converterManager.getEventDataConverter(capabilities.getProtocolVersion())); - - if (messages.getEvents().isEmpty()) { - // No events were generated, we are done - LOG.info(robotName + ": no events were generated"); - return; - } - - LOG.info(robotName + ": sending events"); - List<OperationRequest> response = - connector.sendMessageBundle(messages, this, capabilities.getProtocolVersion()); - LOG.info(robotName + ": received operations"); - - operationApplicator.applyOperations( - response, wavelet.getSnapshotAfterDeltas(), wavelet.getVersionAfterDeltas(), account); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/RobotConnector.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/RobotConnector.java b/src/org/waveprotocol/box/server/robots/passive/RobotConnector.java deleted file mode 100644 index 355196f..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/RobotConnector.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 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.passive; - -import com.google.inject.Inject; -import com.google.wave.api.InvalidRequestException; -import com.google.wave.api.OperationRequest; -import com.google.wave.api.ProtocolVersion; -import com.google.wave.api.RobotSerializer; -import com.google.wave.api.impl.EventMessageBundle; -import com.google.wave.api.robot.CapabilityFetchException; -import com.google.wave.api.robot.RobotCapabilitiesParser; -import com.google.wave.api.robot.RobotConnection; -import com.google.wave.api.robot.RobotConnectionException; - -import org.waveprotocol.box.server.account.RobotAccountData; -import org.waveprotocol.box.server.account.RobotAccountDataImpl; -import org.waveprotocol.box.server.robots.RobotCapabilities; -import org.waveprotocol.wave.util.logging.Log; - -import java.util.Collections; -import java.util.List; - -/** - * This class sends {@link EventMessageBundle} to a robot and receives their - * response. It will gracefully handle failure by acting like the robot sent no - * operations. - * - * @author [email protected] (Lennard de Rijk) - */ -public class RobotConnector { - - private static final Log LOG = Log.get(RobotConnector.class); - - private final RobotConnection connection; - - private final RobotSerializer serializer; - - @Inject - public RobotConnector(RobotConnection connection, RobotSerializer serializer) { - this.connection = connection; - this.serializer = serializer; - } - - /** - * Synchronously sends an {@link EventMessageBundle} off to a robot and hands - * back the response in the form of a list of {@link OperationRequest}s. - * - * @param bundle the bundle to send to the robot. - * @param robot the {@link RobotAccountData} of the robot. - * @param version the version that we should speak to the robot. - * @returns list of {@link OperationRequest}s that the robot wants to have - * executed. - */ - public List<OperationRequest> sendMessageBundle( - EventMessageBundle bundle, Robot robot, ProtocolVersion version) { - String serializedBundle = serializer.serialize(bundle, version); - - String robotUrl = robot.getAccount().getUrl() + Robot.RPC_URL; - LOG.info("Sending: " + serializedBundle + " to " + robotUrl); - - try { - String response = connection.postJson(robotUrl, serializedBundle); - LOG.info("Received: " + response + " from " + robotUrl); - return serializer.deserializeOperations(response); - } catch (RobotConnectionException e) { - LOG.info("Failed to receive a response from " + robotUrl, e); - } catch (InvalidRequestException e) { - LOG.info("Failed to deserialize passive API response", e); - } - - // Return an empty list and let the caller ignore the failure - return Collections.emptyList(); - } - - /** - * Returns a new {@link RobotAccountData} updated with the new capabilities - * using the given {@link RobotAccountData}. - * - * @param account The {@link RobotAccountData} to update the capabilities for. - * @param activeApiUrl the url of the Active Robot API. - * @throws CapabilityFetchException if the capabilities couldn't be retrieved - * or parsed. - */ - public RobotAccountData fetchCapabilities(RobotAccountData account, String activeApiUrl) - throws CapabilityFetchException { - RobotCapabilitiesParser parser = new RobotCapabilitiesParser( - account.getUrl() + Robot.CAPABILITIES_URL, connection, activeApiUrl); - RobotCapabilities capabilities = new RobotCapabilities( - parser.getCapabilities(), parser.getCapabilitiesHash(), parser.getProtocolVersion()); - - return new RobotAccountDataImpl(account.getId(), account.getUrl(), account.getConsumerSecret(), - capabilities, account.isVerified()); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/RobotOperationApplicator.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/RobotOperationApplicator.java b/src/org/waveprotocol/box/server/robots/passive/RobotOperationApplicator.java deleted file mode 100644 index efb40b8..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/RobotOperationApplicator.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 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.passive; - -import com.google.wave.api.OperationRequest; -import com.google.wave.api.ProtocolVersion; -import com.google.wave.api.data.converter.EventDataConverterManager; -import com.google.wave.api.event.EventType; -import com.google.wave.api.event.OperationErrorEvent; -import org.waveprotocol.box.server.account.RobotAccountData; -import org.waveprotocol.box.server.robots.OperationContext; -import org.waveprotocol.box.server.robots.OperationContextImpl; -import org.waveprotocol.box.server.robots.OperationResults; -import org.waveprotocol.box.server.robots.OperationServiceRegistry; -import org.waveprotocol.box.server.robots.RobotWaveletData; -import org.waveprotocol.box.server.robots.operations.OperationService; -import org.waveprotocol.box.server.robots.util.ConversationUtil; -import org.waveprotocol.box.server.robots.util.LoggingRequestListener; -import org.waveprotocol.box.server.robots.util.OperationUtil; -import org.waveprotocol.box.server.waveserver.WaveletProvider; -import org.waveprotocol.wave.model.version.HashedVersion; -import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; -import org.waveprotocol.wave.util.logging.Log; - -import java.util.List; - -/** - * This class is responsible for applying operations received from a Robot in - * the Passive API. Operations that fail will result in an - * {@link OperationErrorEvent} being generated and they might be sent off to the - * robot after all of the robot's operations have been completed. - * - * @author [email protected] (Lennard de Rijk) - */ -public class RobotOperationApplicator { - - private static final Log LOG = Log.get(RobotOperationApplicator.class); - - private final static WaveletProvider.SubmitRequestListener LOGGING_REQUEST_LISTENER = - new LoggingRequestListener(LOG); - - private final EventDataConverterManager converterManager; - private final WaveletProvider waveletProvider; - private final OperationServiceRegistry operationRegistry; - private final ConversationUtil conversationUtil; - - /** - * Constructs a new {@link RobotOperationApplicator}. - * - * @param converterManager used to convert to Robot API objects - * @param waveletProvider used to retrieve wavelets and submit deltas - * @param operationRegistry registry containing the {@link OperationService}s - * that this applicator can perform. - * @param conversationUtil used to create conversations. - */ - public RobotOperationApplicator(EventDataConverterManager converterManager, - WaveletProvider waveletProvider, OperationServiceRegistry operationRegistry, - ConversationUtil conversationUtil) { - this.converterManager = converterManager; - this.waveletProvider = waveletProvider; - this.operationRegistry = operationRegistry; - this.conversationUtil = conversationUtil; - } - - /** - * Applies the operations within the context of the wavelet. - * - * @param operations the operations to apply - * @param wavelet the wavelet which is the context in which this operation is - * performed. - * @param hashedVersion the version of the wavelet to which to apply the - * operations to. - * @param account the account for which to apply robot operations. - */ - void applyOperations(List<OperationRequest> operations, ReadableWaveletData wavelet, - HashedVersion hashedVersion, RobotAccountData account) { - // The robots we support should be sending us their version in their first - // operation - ProtocolVersion protocolVersion = OperationUtil.getProtocolVersion(operations); - - OperationContextImpl context = new OperationContextImpl(waveletProvider, - converterManager.getEventDataConverter(protocolVersion), conversationUtil, - new RobotWaveletData(wavelet, hashedVersion)); - - executeOperations(context, operations, account); - handleResults(context, account); - } - - /** - * Executes operations in the given context. - * - * @param context the context to perform the operations in. - * @param operations the operations to perform. - * @param account the account for which to execute robot operations. - */ - private void executeOperations( - OperationContext context, List<OperationRequest> operations, RobotAccountData account) { - for (OperationRequest operation : operations) { - // Get the operation of the author taking into account the proxying for - // field. - OperationUtil.executeOperation(operation, operationRegistry, context, account.getId()); - } - } - - /** - * Handles an {@link OperationResults} by submitting the deltas it generates - * and sending off any events to the robot. Note that currently no events are - * send off to the robot. - * - * @param results the results of the operations performed - * @param account the account for which to handle results of robot operations. - */ - private void handleResults(OperationResults results, RobotAccountData account) { - OperationUtil.submitDeltas(results, waveletProvider, LOGGING_REQUEST_LISTENER); - - // TODO(ljvderijk): In theory we should be sending off all events that are - // generated by the operations. Currently not done in production. We should - // make it possible though. - boolean notifyOnError = - account.getCapabilities().getCapabilitiesMap().containsKey(EventType.OPERATION_ERROR); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/RobotsGateway.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/RobotsGateway.java b/src/org/waveprotocol/box/server/robots/passive/RobotsGateway.java deleted file mode 100644 index d2a08b1..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/RobotsGateway.java +++ /dev/null @@ -1,229 +0,0 @@ -/** - * 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.passive; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.inject.Inject; -import com.google.inject.name.Named; -import com.google.wave.api.RobotSerializer; -import com.google.wave.api.data.converter.EventDataConverterManager; -import com.google.wave.api.robot.CapabilityFetchException; -import com.google.wave.api.robot.RobotName; - -import org.waveprotocol.box.common.DeltaSequence; -import org.waveprotocol.box.server.account.AccountData; -import org.waveprotocol.box.server.account.RobotAccountData; -import org.waveprotocol.box.server.persistence.AccountStore; -import org.waveprotocol.box.server.persistence.PersistenceException; -import org.waveprotocol.box.server.robots.operations.NotifyOperationService; -import org.waveprotocol.box.server.robots.util.ConversationUtil; -import org.waveprotocol.box.server.waveserver.WaveBus; -import org.waveprotocol.box.server.waveserver.WaveletProvider; -import org.waveprotocol.wave.model.id.WaveletName; -import org.waveprotocol.wave.model.operation.OperationException; -import org.waveprotocol.wave.model.operation.wave.AddParticipant; -import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; -import org.waveprotocol.wave.model.operation.wave.WaveletOperation; -import org.waveprotocol.wave.model.version.HashedVersion; -import org.waveprotocol.wave.model.wave.ParticipantId; -import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; -import org.waveprotocol.wave.util.logging.Log; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Executor; - - -/** - * Gateway for the Passive Robot API, this class can be subscribed to the - * WaveBus and fires of separate threads to handle any updates for Robots. - * - * @author [email protected] (Lennard de Rijk) - */ -public class RobotsGateway implements WaveBus.Subscriber { - - private static final Log LOG = Log.get(RobotsGateway.class); - - private final WaveletProvider waveletProvider; - private final AccountStore accountStore; - private final EventDataConverterManager converterManager; - private final RobotConnector connector; - private final Map<RobotName, Robot> allRobots = Maps.newHashMap(); - private final Set<RobotName> runnableRobots = Sets.newHashSet(); - private final Executor executor; - private final ConversationUtil conversationUtil; - private final NotifyOperationService notifyOpService; - - @Inject - @VisibleForTesting - RobotsGateway(WaveletProvider waveletProvider, RobotConnector connector, - AccountStore accountStore, RobotSerializer serializer, - EventDataConverterManager converterManager, @Named("GatewayExecutor") Executor executor, - ConversationUtil conversationUtil, NotifyOperationService notifyOpService) { - this.waveletProvider = waveletProvider; - this.accountStore = accountStore; - this.converterManager = converterManager; - this.connector = connector; - this.executor = executor; - this.conversationUtil = conversationUtil; - this.notifyOpService = notifyOpService; - } - - @Override - public void waveletCommitted(WaveletName waveletName, HashedVersion version) { - // We ignore this event. - } - - @Override - public void waveletUpdate(ReadableWaveletData wavelet, DeltaSequence deltas) { - Set<ParticipantId> currentAndNewParticipants = Sets.newHashSet(wavelet.getParticipants()); - for (TransformedWaveletDelta delta : deltas) { - // Participants added or removed in this delta get the whole delta. - for (WaveletOperation op : delta) { - if (op instanceof AddParticipant) { - ParticipantId p = ((AddParticipant) op).getParticipantId(); - currentAndNewParticipants.add(p); - } - } - } - // Robot should receive also deltas that contain AddParticipant ops. - // EventGenerator will take care to filter out events before the add. - for (ParticipantId participant : currentAndNewParticipants) { - RobotName robotName = RobotName.fromAddress(participant.getAddress()); - if (robotName == null) { - // Not a valid robot name, next. - continue; - } - - ParticipantId robotId = ParticipantId.ofUnsafe(robotName.toEmailAddress()); - AccountData account; - try { - account = accountStore.getAccount(robotId); - } catch (PersistenceException e) { - LOG.severe("Failed to retrieve the account data for " + robotId.getAddress(), e); - continue; - } - - if (account != null && account.isRobot()) { - RobotAccountData robotAccount = account.asRobot(); - if (robotAccount.isVerified()) { - Robot robot = getOrCreateRobot(robotName, robotAccount); - updateRobot(robot, wavelet, deltas); - } - } - } - } - - /** - * Gets or creates a {@link Robot} for the given name. - * - * @param robotName the name of the robot. - * @param account the {@link RobotAccountData} belonging to the given - * {@link RobotName}. - */ - private Robot getOrCreateRobot(RobotName robotName, RobotAccountData account) { - Robot robot = allRobots.get(robotName); - - if (robot == null) { - robot = createNewRobot(robotName, account); - allRobots.put(robotName, robot); - } - return robot; - } - - /** - * Creates a new {@link Robot}. - * - * @param robotName the name of the robot. - * @param account the {@link RobotAccountData} belonging to the given - * {@link RobotName}. - */ - private Robot createNewRobot(RobotName robotName, RobotAccountData account) { - EventGenerator eventGenerator = new EventGenerator(robotName, conversationUtil); - RobotOperationApplicator operationApplicator = - new RobotOperationApplicator(converterManager, waveletProvider, - new OperationServiceRegistryImpl(notifyOpService), conversationUtil); - return new Robot(robotName, account, this, connector, converterManager, waveletProvider, - eventGenerator, operationApplicator); - } - - /** - * Updates a {@link Robot} with information about a waveletUpdate event. - * - * @param robot The robot to process the update for. - * @param wavelet the wavelet on which the update is occuring. - * @param deltas the deltas the have been applied to the given wavelet. - */ - private void updateRobot(Robot robot, ReadableWaveletData wavelet, DeltaSequence deltas) { - try { - robot.waveletUpdate(wavelet, deltas); - ensureScheduled(robot); - } catch (OperationException e) { - LOG.warning("Unable to update robot(" + robot.getRobotName() + ")", e); - } - } - - /** - * Ensures that a robot is submitted to the executor, might submit the - * {@link Robot} if it hasn't been submitted yet. - * - * <p> - * Synchronized in combination with done() to keep proper track of the robots - * that have been submitted. - * - * @param robot the {@link Robot} to enqueue - */ - public synchronized void ensureScheduled(Robot robot) { - if (!runnableRobots.contains(robot.getRobotName())) { - LOG.info("Enqueing robot: " + robot.getRobotName()); - runnableRobots.add(robot.getRobotName()); - executor.execute(robot); - } - } - - /** - * Signal that a robot is done running. Synchronized with ensureRunnable since - * that method needs to have a synchronized view on the runnableRobots for - * submitting task to the executor. - * - * @param robot the {@link Robot} which is done working. - */ - public synchronized void doneRunning(Robot robot) { - runnableRobots.remove(robot.getRobotName()); - } - - /** - * Updates the account for the given {@link Robot}. - * - * @param robot the {@link Robot} to update. - * @throws CapabilityFetchException if the capabilities could not be fetched - * or parsed. - */ - public void updateRobotAccount(Robot robot) throws CapabilityFetchException, - PersistenceException { - // TODO: Pass in activeAPIUrl - String activeApiUrl = ""; - RobotAccountData newAccount = connector.fetchCapabilities(robot.getAccount(), activeApiUrl); - accountStore.putAccount(newAccount); - robot.setAccount(newAccount); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/WaveletAndDeltas.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/WaveletAndDeltas.java b/src/org/waveprotocol/box/server/robots/passive/WaveletAndDeltas.java deleted file mode 100644 index 1b4fbf6..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/WaveletAndDeltas.java +++ /dev/null @@ -1,218 +0,0 @@ -/** - * 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.passive; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; - -import org.waveprotocol.box.common.DeltaSequence; -import org.waveprotocol.box.server.util.WaveletDataUtil; -import org.waveprotocol.wave.model.id.WaveletName; -import org.waveprotocol.wave.model.operation.OperationException; -import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; -import org.waveprotocol.wave.model.operation.wave.WaveletDelta; -import org.waveprotocol.wave.model.operation.wave.WaveletOperation; -import org.waveprotocol.wave.model.version.HashedVersion; -import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; -import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; -import org.waveprotocol.wave.model.wave.data.WaveletData; - -import java.util.List; - -/** - * A wavelet snapshot and a sequence of deltas applying to that snapshot. - * - * @author [email protected] (Lennard de Rijk) - */ -public class WaveletAndDeltas { - - /** - * Snapshot of the wavelet before any deltas are applied. - */ - private final ReadableWaveletData snapshotBeforeDeltas; - - /** - * Contiguous deltas applying to snapshotBeforeDeltas. - */ - private DeltaSequence deltas; - - /** - * Cached result of applying all deltas to the first snapshot. - */ - private ObservableWaveletData snapshotAfterDeltas; - - /** - * The name of the wavelet in this container. - */ - private final WaveletName waveletName; - - /** - * Constructs a {@link WaveletAndDeltas} from wavelet data and a tail of the - * sequence of transformed deltas leading to that snapshot. Takes a copy of - * the WaveletData so that operations can happily applied to it. - * - * The resulting version of the last delta must match the snapshot's version. - * - * @param snapshot the state of the wavelet after the deltas have been - * applied. - * @param deltas the deltas in the order they have been applied to the - * wavelet. - * @throws OperationException if the operations can not be rolled back to - * create a snapshot before the deltas have been applied. - */ - public static WaveletAndDeltas create(ReadableWaveletData snapshot, DeltaSequence deltas) - throws OperationException { - HashedVersion endVersion = deltas.isEmpty() ? snapshot.getHashedVersion() : - deltas.getEndVersion(); - Preconditions.checkArgument(snapshot.getVersion() == endVersion.getVersion(), - String.format("Version of snapshot %s doesn't match the end version %s", - snapshot.getVersion(), endVersion)); - - ObservableWaveletData preDeltaWavelet = WaveletDataUtil.copyWavelet(snapshot); - rollback(preDeltaWavelet, deltas); - ObservableWaveletData postDeltaWavelet = WaveletDataUtil.copyWavelet(snapshot); - return new WaveletAndDeltas(preDeltaWavelet, postDeltaWavelet, deltas); - } - - /** - * Reverses the operations detailed in the list of deltas on the given - * wavelet. - * - * @param wavelet {@link ObservableWaveletData} to apply operations to - * @param deltas the {@link WaveletDelta} containing the operations - * which we should revert on the given wavelet. - * @throws OperationException if the operations can not be rolled back. - */ - private static void rollback(ObservableWaveletData wavelet, List<TransformedWaveletDelta> deltas) - throws OperationException { - List<WaveletOperation> inverseOps = Lists.newArrayList(); - - // Go through everything in reverse order - for (int i = deltas.size() - 1; i >= 0; i--) { - TransformedWaveletDelta delta = deltas.get(i); - // Metadata such as the last modified ts will change due to the rollback - // of operations. - for (int j = delta.size() - 1; j >= 0; j--) { - WaveletOperation op = delta.get(j); - WaveletOperation inverseOp = WaveletOperationInverter.invert(op); - inverseOps.add(inverseOp); - } - } - - long startVersion = wavelet.getVersion(); - int opCount = 0; - for (WaveletOperation inverseOp : inverseOps) { - inverseOp.apply(wavelet); - opCount++; - } - if (wavelet.getVersion() != startVersion - opCount) { - throw new OperationException("Expected end version " + (startVersion - opCount) - + " doesn't match the version of the wavelet " + wavelet.getVersion()); - } - } - - /** - * Constructs a {@link WaveletAndDeltas} from the given {@link WaveletData} - * and {@link WaveletDelta}s. - * - * @param preDeltasSnapshot the state of the wavelet before the deltas have - * been applied. - * @param postDeltasSnapshot the state of the wavelet after the deltas have - * been applied. - * @param deltas deltas in the order they have been applied to the wavelet. - */ - private WaveletAndDeltas(ObservableWaveletData preDeltasSnapshot, - ObservableWaveletData postDeltasSnapshot, DeltaSequence deltas) { - this.snapshotBeforeDeltas = preDeltasSnapshot; - this.deltas = deltas; - this.snapshotAfterDeltas = postDeltasSnapshot; - this.waveletName = WaveletDataUtil.waveletNameOf(preDeltasSnapshot); - } - - /** - * Returns the wavelet before any deltas have been applied. - */ - public ReadableWaveletData getSnapshotBeforeDeltas() { - return snapshotBeforeDeltas; - } - - /** - * Returns all deltas collected. - */ - public DeltaSequence getDeltas() { - return deltas; - } - - /** - * Returns the latest snapshot with all deltas applied. - */ - public ReadableWaveletData getSnapshotAfterDeltas() { - return snapshotAfterDeltas; - } - - /** - * Returns the {@link HashedVersion} of the wavelet after all deltas have been - * applied. - */ - public HashedVersion getVersionAfterDeltas() { - return deltas.isEmpty() ? snapshotAfterDeltas.getHashedVersion() : deltas.getEndVersion(); - } - - /** - * Appends the given deltas to the deltas already stored. Updates the latest - * snapshot and latest version as well. This method will make a copy of the - * snapshot. - * - * @param updatedSnapshot the snapshot after deltas have been applied - * @param newDeltas the deltas that have been applied since the last call to - * appendDeltas. - */ - public void appendDeltas(ReadableWaveletData updatedSnapshot, - DeltaSequence newDeltas) { - HashedVersion newEndVersion = newDeltas.getEndVersion(); - Preconditions.checkArgument( - !newDeltas.isEmpty(), "There were no new deltas passed to appendDeltas"); - Preconditions.checkArgument(updatedSnapshot.getVersion() == newEndVersion.getVersion(), - String.format("Version of snapshot %s doesn't match the HashedVersion %s", - updatedSnapshot.getVersion(), newEndVersion)); - Preconditions.checkArgument(areContiguousToCurrentVersion(newDeltas), String.format( - "Deltas are not contiguous to the current version(%s) %s", getVersionAfterDeltas(), deltas)); - WaveletName updatedWaveletName = WaveletDataUtil.waveletNameOf(updatedSnapshot); - Preconditions.checkArgument(updatedWaveletName.equals(waveletName), - String.format( - "Updated wavelet doesn't have the same name as with which this class has been " - + "instantiated. %s != %s", updatedWaveletName, waveletName)); - - // TODO(ljvderijk): This should actually be applying the deltas, however - // they do not contain a timestamp at this time. - snapshotAfterDeltas = WaveletDataUtil.copyWavelet(updatedSnapshot); - deltas = DeltaSequence.join(deltas, newDeltas); - } - - /** - * Returns true if the given deltas apply to the current version of this - * wavelet and they are contiguous. - * - * @param deltas the list of deltas to check. - */ - public boolean areContiguousToCurrentVersion(DeltaSequence deltas) { - return deltas.getStartVersion() == getVersionAfterDeltas().getVersion(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7d8609e7/src/org/waveprotocol/box/server/robots/passive/WaveletOperationInverter.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/robots/passive/WaveletOperationInverter.java b/src/org/waveprotocol/box/server/robots/passive/WaveletOperationInverter.java deleted file mode 100644 index 3b7e124..0000000 --- a/src/org/waveprotocol/box/server/robots/passive/WaveletOperationInverter.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * 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.passive; - -import org.waveprotocol.wave.model.document.operation.algorithm.DocOpInverter; -import org.waveprotocol.wave.model.operation.wave.AddParticipant; -import org.waveprotocol.wave.model.operation.wave.BlipContentOperation; -import org.waveprotocol.wave.model.operation.wave.BlipOperation; -import org.waveprotocol.wave.model.operation.wave.BlipOperationVisitor; -import org.waveprotocol.wave.model.operation.wave.NoOp; -import org.waveprotocol.wave.model.operation.wave.RemoveParticipant; -import org.waveprotocol.wave.model.operation.wave.SubmitBlip; -import org.waveprotocol.wave.model.operation.wave.VersionUpdateOp; -import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; -import org.waveprotocol.wave.model.operation.wave.WaveletOperation; -import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; -import org.waveprotocol.wave.model.operation.wave.WaveletOperationVisitor; - -/** - * Approximately inverts an applied wavelet operation. - * - * The only contract this inverter provides is that: - * - * <pre class="code">invert(o) ; o == id </pre> - * - * i.e., given any state {@code s} that was reached by an operation {@code o}, - * the "previous" state {@code s'} can be constructed {@code s' = invert(o)(s)} - * such that applying {@code o} to it will return to the given state {@code s}. - * - * This inverter does not accept operations that do not appear in a wavelet's - * history (e.g., {@link VersionUpdateOp}). - * - * TODO(anorth): Move to {@link org.waveprotocol.wave.model.operation.wave}. - * - * @author [email protected] (David Hearnden) - */ -final class WaveletOperationInverter implements WaveletOperationVisitor { - - /** - * Inverts a blip operation, ignoring metadata. - */ - final static class BlipOperationInverter implements BlipOperationVisitor { - private final WaveletOperationContext reverseContext; - private BlipOperation inverse; - - BlipOperationInverter(WaveletOperationContext reverseContext) { - this.reverseContext = reverseContext; - } - - static BlipOperation invert(WaveletOperationContext reverseContext, BlipOperation op) { - return new BlipOperationInverter(reverseContext).visit(op); - } - - private BlipOperation visit(BlipOperation op) { - op.acceptVisitor(this); - return inverse; - } - - @Override - public void visitBlipContentOperation(BlipContentOperation op) { - inverse = new BlipContentOperation(reverseContext, DocOpInverter.invert(op.getContentOp())); - } - - @Override - public void visitSubmitBlip(SubmitBlip op) { - inverse = new SubmitBlip(reverseContext); - } - } - - private final WaveletOperationContext reverseContext; - private WaveletOperation inverse; - - WaveletOperationInverter(WaveletOperationContext reverseContext) { - this.reverseContext = reverseContext; - } - - /** - * Inverts an operation. - */ - static WaveletOperation invert(WaveletOperation op) { - WaveletOperationContext forwardContext = op.getContext(); - WaveletOperationContext reverseContext = new WaveletOperationContext( // - forwardContext.getCreator(), - // Lie, and keep the same modification time. - forwardContext.getTimestamp(), - // Correctly invert the version increment. - -forwardContext.getVersionIncrement(), - // Lie again, and report the hashed version as the same as after op. - // This makes it out of sync with the version number. - forwardContext.getHashedVersion()); - return new WaveletOperationInverter(reverseContext).visit(op); - } - - private WaveletOperation visit(WaveletOperation op) { - op.acceptVisitor(this); - return inverse; - } - - @Override - public void visitWaveletBlipOperation(WaveletBlipOperation op) { - inverse = new WaveletBlipOperation( - op.getBlipId(), BlipOperationInverter.invert(reverseContext, op.getBlipOp())); - } - - @Override - public void visitVersionUpdateOp(VersionUpdateOp op) { - throw new UnsupportedOperationException(); - } - - @Override - public void visitAddParticipant(AddParticipant op) { - inverse = new RemoveParticipant(reverseContext, op.getParticipantId()); - } - - @Override - public void visitRemoveParticipant(RemoveParticipant op) { - inverse = new AddParticipant(reverseContext, op.getParticipantId()); - } - - @Override - public void visitNoOp(NoOp op) { - inverse = new NoOp(reverseContext); - } -}
