cc.kune.wave.server.kspecific.KuneWaveServiceDefault.java Source code

Java tutorial

Introduction

Here is the source code for cc.kune.wave.server.kspecific.KuneWaveServiceDefault.java

Source

/*
 *
 * Copyright (C) 2007-2015 Licensed to the Comunes Association (CA) under
 * one or more contributor license agreements (see COPYRIGHT for details).
 * The CA licenses this file to you under the GNU Affero General Public
 * License version 3, (the "License"); you may not use this file except in
 * compliance with the License. This file is part of kune.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package cc.kune.wave.server.kspecific;

import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.Nonnull;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.waveprotocol.box.server.CoreSettings;
import org.waveprotocol.box.server.robots.OperationContextImpl;
import org.waveprotocol.box.server.robots.OperationServiceRegistry;
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.box.server.waveserver.WaveletProvider.SubmitRequestListener;
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.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.waveref.WaveRef;

import cc.kune.common.shared.utils.SimpleArgCallback;
import cc.kune.common.shared.utils.TextUtils;
import cc.kune.core.client.errors.AccessViolationException;
import cc.kune.core.client.errors.DefaultException;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.wave.api.Annotation;
import com.google.wave.api.ApiIdSerializer;
import com.google.wave.api.Blip;
import com.google.wave.api.BlipContent;
import com.google.wave.api.BlipData;
import com.google.wave.api.BlipThread;
import com.google.wave.api.Element;
import com.google.wave.api.ElementType;
import com.google.wave.api.Gadget;
import com.google.wave.api.JsonRpcConstant.ParamsProperty;
import com.google.wave.api.JsonRpcResponse;
import com.google.wave.api.OperationQueue;
import com.google.wave.api.OperationRequest;
import com.google.wave.api.OperationRequest.Parameter;
import com.google.wave.api.Participants;
import com.google.wave.api.ProtocolVersion;
import com.google.wave.api.Range;
import com.google.wave.api.Wavelet;
import com.google.wave.api.data.converter.EventDataConverterManager;
import com.google.wave.api.impl.DocumentModifyAction;
import com.google.wave.api.impl.DocumentModifyAction.BundledAnnotation;
import com.google.wave.api.impl.DocumentModifyAction.ModifyHow;
import com.google.wave.api.impl.DocumentModifyQuery;
import com.google.wave.api.impl.WaveletData;
import com.google.wave.splash.rpc.ClientAction;
import com.google.wave.splash.web.template.WaveRenderer;

public class KuneWaveServiceDefault implements KuneWaveService {
    public static final Log LOG = LogFactory.getLog(KuneWaveServiceDefault.class);

    // See: DocumentModifyServiceTest
    private static final String NO_ANNOTATION_KEY = null;
    private static final List<BundledAnnotation> NO_BUNDLED_ANNOTATIONS = Collections.emptyList();
    private static final List<String> NO_VALUES = Collections.<String>emptyList();

    /**
     *
     * Copy blips
     *
     * @param fromBlip
     * @param toBlip
     *
     * @author yurize@apache.org (Yuri Zelikov)
     *
     */
    public static void copyWavelet(final Blip fromBlip, final Blip toBlip) {
        for (final BlipContent blipContent : fromBlip.all().values()) {
            toBlip.append(blipContent);
        }
        // Deep copy annotations
        for (final Annotation annotation : fromBlip.getAnnotations()) {
            final Range range = annotation.getRange();
            try {
                toBlip.range(range.getStart() + 1, range.getEnd() + 1).annotate(annotation.getName(),
                        annotation.getValue());
            } catch (final IndexOutOfBoundsException e) {
                // Don't copy faulty annotations
            }
        }
    }

    public static void copyWaveletElements(final Blip fromBlip, final Blip toBlip) {
        // Deep copy form elements.
        // DocumentModifyService don't permit this:
        // "Can't insert other elements than text and gadgets at the moment");
        for (final Entry<Integer, Element> entry : fromBlip.getElements().entrySet()) {
            final ElementType type = entry.getValue().getType();
            Element result = null;
            // if (FormElement.getFormElementTypes().contains(type)) {
            // result = new FormElement(type, entry.getValue().getProperties());
            // } else

            if (type == ElementType.GADGET) {
                result = new Gadget(entry.getValue().getProperties());
                // } else if (type == ElementType.IMAGE) {
                // result = new Image(entry.getValue().getProperties());
                // } else if (type == ElementType.LINE) {
                // result = new Line(entry.getValue().getProperties());
                // } else {
                // result = new Element(type, entry.getValue().getProperties());
            }
            if (result != null) {
                toBlip.append(result);
            }
        }
    }

    private final ConversationUtil conversationUtil;
    private final EventDataConverterManager converterManager;
    private final String domain;
    private final OperationServiceRegistry operationRegistry;
    private final ParticipantUtils participantUtils;
    private final WaveletProvider waveletProvider;
    private final WaveRenderer waveRenderer;

    @Inject
    public KuneWaveServiceDefault(final EventDataConverterManager converterManager,
            @Named("DataApiRegistry") final OperationServiceRegistry operationRegistry,
            final WaveletProvider waveletProvider, final ConversationUtil conversationUtil,
            final ParticipantUtils participantUtils, final WaveRenderer waveRenderer,
            @Named(CoreSettings.WAVE_SERVER_DOMAIN) final String domain) {
        this.converterManager = converterManager;
        this.waveletProvider = waveletProvider;
        this.conversationUtil = conversationUtil;
        this.operationRegistry = operationRegistry;
        this.participantUtils = participantUtils;
        this.waveRenderer = waveRenderer;
        this.domain = domain;
    }

    @Override
    public void addGadget(final WaveRef waveName, final String author, final URL gadgetUrl) {
        // See DocumentModifyServiceTest
        final List<Element> elementsIn = Lists.newArrayListWithCapacity(1);
        final Map<String, String> properties = Maps.newHashMap();
        properties.put(Gadget.URL, gadgetUrl.toString());
        properties.put(Gadget.AUTHOR, participantUtils.of(author).getAddress());
        final Gadget gadget = new Gadget(properties);

        elementsIn.add(gadget);
        final Wavelet wavelet = fetchWave(waveName, author);
        final OperationQueue opQueue = new OperationQueue();
        final Blip rootBlip = wavelet.getRootBlip();

        final OperationRequest operationRequest = opQueue.modifyDocument(rootBlip);
        operationRequest
                .addParameter(Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT,
                        NO_VALUES, NO_ANNOTATION_KEY, elementsIn, NO_BUNDLED_ANNOTATIONS, false)));
        operationRequest.addParameter(Parameter.of(ParamsProperty.INDEX, 1));
        doOperations(author, opQueue, "add gadget");
    }

    @Override
    public boolean addParticipants(final WaveRef waveName, final String author, final String userWhoAdds,
            final String... newLocalParticipants) {
        boolean added = false;
        final Wavelet wavelet = fetchWave(waveName, author);
        final Participants currentParticipants = wavelet.getParticipants();
        final String whoAdd = wavelet.getParticipants().contains(participantUtils.of(userWhoAdds)) ? userWhoAdds
                : author;
        final OperationQueue opQueue = new OperationQueue();
        for (final String participant : participantUtils.toSet(newLocalParticipants)) {
            final String newPartWithDomain = participantUtils.of(participant).toString();
            // Removing duplicates
            if (!currentParticipants.contains(newPartWithDomain)) {
                // FIXME This is very costly. Seems like only one participant per
                // opQueue is added (try to
                // fix this in WAVE)
                LOG.debug("Adding as participant: " + newPartWithDomain);

                opQueue.addParticipantToWavelet(wavelet, newPartWithDomain);
                added = true;
            }
        }
        doOperations(whoAdd, opQueue, "add participant");
        return added;
    }

    @Override
    public WaveRef createWave(final String message, final SimpleArgCallback<WaveRef> onCreate,
            final ParticipantId... participants) {
        return createWave(NO_TITLE, message, onCreate, participants);
    }

    @Override
    public WaveRef createWave(@Nonnull final String title, final String message,
            final SimpleArgCallback<WaveRef> onCreate, @Nonnull final ParticipantId... participantsArray) {
        return createWave(title, message, onCreate, WITHOUT_GADGET, participantsArray);
    }

    @Override
    public WaveRef createWave(final String title, final String message, final SimpleArgCallback<WaveRef> onCreate,
            final String... participantsArray) {
        return createWave(title, message, onCreate, participantUtils.listFrom(participantsArray));
    }

    @Override
    public WaveRef createWave(final String title, final String message, final SimpleArgCallback<WaveRef> onCreate,
            final URL gadgetUrl, final Map<String, String> gadgetProperties,
            final ParticipantId... participantsArray) {
        return createWave(title, message, NO_WAVE_TO_COPY, onCreate, gadgetUrl, gadgetProperties,
                participantsArray);
    }

    @Override
    public WaveRef createWave(@Nonnull final String title, final String message,
            final SimpleArgCallback<WaveRef> onCreate, final URL gadgetUrl,
            @Nonnull final ParticipantId... participantsArray) {
        return createWave(title, message, NO_WAVE_TO_COPY, onCreate, gadgetUrl, participantsArray);
    }

    @Override
    public WaveRef createWave(@Nonnull final String title, final String message, final WaveRef waveIdToCopy,
            final SimpleArgCallback<WaveRef> onCreate, final URL gadgetUrl,
            final Map<String, String> gadgetProperties, @Nonnull final ParticipantId... participantsArray) {
        String newWaveId = null;
        String newWaveletId = null;
        final Set<String> participants = new HashSet<String>();
        for (final ParticipantId participant : participantsArray) {
            participants.add(participant.toString());
        }
        final ParticipantId user = participantsArray[0];
        final OperationQueue opQueue = new OperationQueue();
        final Wavelet newWavelet = opQueue.createWavelet(domain, participants);
        opQueue.setTitleOfWavelet(newWavelet, title);
        final Blip rootBlip = newWavelet.getRootBlip();
        rootBlip.append(new com.google.wave.api.Markup(message).getText());

        if (waveIdToCopy != NO_WAVE_TO_COPY) {
            try {
                // WaveId copyWaveId;
                // copyWaveId = WaveId.ofChecked(domain, waveIdToCopy);
                final Wavelet waveletToCopy = fetchWave(waveIdToCopy.getWaveId(), waveIdToCopy.getWaveletId(),
                        participantsArray[0].toString());
                if (waveletToCopy != null) {
                    copyWavelet(waveletToCopy.getRootBlip(), rootBlip);
                    // copyWaveletElements(waveletToCopy.getRootBlip(), rootBlip);
                }
            } catch (final DefaultException e2) {
                LOG.error("Error copying wave content", e2);
            }
        }

        if (gadgetUrl != WITHOUT_GADGET) {
            Gadget gadget;
            if (gadgetProperties.size() > 0) {
                gadgetProperties.put(Gadget.URL, gadgetUrl.toString());
                gadget = new Gadget(gadgetProperties);
            } else {
                gadget = new Gadget(gadgetUrl.toString());
            }
            rootBlip.append(gadget);
        }

        final OperationContextImpl context = new OperationContextImpl(waveletProvider,
                converterManager.getEventDataConverter(ProtocolVersion.DEFAULT), conversationUtil);
        for (final OperationRequest req : opQueue.getPendingOperations()) {
            OperationUtil.executeOperation(req, operationRegistry, context, user);
            final String reqId = req.getId();
            final JsonRpcResponse response = context.getResponse(reqId);
            if (response != null) {
                if (response.isError()) {
                    final String errorMessage = processErrorMessage(context.getResponse(reqId).getErrorMessage());
                    onFailure(errorMessage);
                    throw new DefaultException(errorMessage);
                } else {
                    final Object responseWaveId = response.getData().get(ParamsProperty.WAVE_ID);
                    final Object responseWaveletId = response.getData().get(ParamsProperty.WAVELET_ID);
                    if (responseWaveId != null && responseWaveletId != null) {
                        // This is serialized use
                        // ApiIdSerializer.instance().deserialiseWaveId (see
                        // WaveService)
                        newWaveId = (String) responseWaveId;
                        newWaveletId = (String) responseWaveletId;
                    }
                }
            }
        }
        WaveRef wavename;
        try {
            wavename = WaveRef.of(ApiIdSerializer.instance().deserialiseWaveId(newWaveId),
                    ApiIdSerializer.instance().deserialiseWaveletId(newWaveletId));
        } catch (final InvalidIdException e) {
            throw new DefaultException("Error getting wave id");
        }
        doSubmit(onCreate, context, wavename);
        LOG.info("WaveId: " + newWaveId + " waveletId: " + newWaveletId);
        return wavename;
    }

    @Override
    public WaveRef createWave(@Nonnull final String title, final String message, final WaveRef waveIdToCopy,
            final SimpleArgCallback<WaveRef> onCreate, final URL gadgetUrl,
            @Nonnull final ParticipantId... participantsArray) {
        return createWave(title, message, waveIdToCopy, onCreate, gadgetUrl, Collections.<String, String>emptyMap(),
                participantsArray);
    }

    @Override
    public boolean delParticipants(final WaveRef waveName, final String whoDel, final Set<String> participants) {
        boolean removed = false;
        final Wavelet wavelet = fetchWave(waveName, whoDel);
        final Participants currentParticipants = wavelet.getParticipants();
        LOG.debug("Removing participants: " + participants.toString());
        final OperationQueue opQueue = new OperationQueue();
        for (final String participant : participants) {
            // FIXME Seems like only one participant per opQueue is added (try to fix
            // this in WAVE)
            final String partWithDomain = participantUtils.of(participant).toString();
            if (currentParticipants.contains(partWithDomain)) {
                LOG.debug("Removing as participant: " + partWithDomain);
                removed = true;
                opQueue.removeParticipantFromWavelet(wavelet, partWithDomain);
            }
        }
        doOperations(whoDel, opQueue, "del participant");
        return removed;
    }

    @Override
    public boolean delParticipants(final WaveRef waveName, final String whoDel, final String... participants) {
        return delParticipants(waveName, whoDel, participantUtils.toSet(participants));
    }

    // final SubmitRequestListener listener
    private void doOperations(final String author, final OperationQueue opQueue, final String logComment) {
        final OperationContextImpl context = new OperationContextImpl(waveletProvider,
                converterManager.getEventDataConverter(ProtocolVersion.DEFAULT), conversationUtil);
        for (final OperationRequest req : opQueue.getPendingOperations()) {
            OperationUtil.executeOperation(req, operationRegistry, context, participantUtils.of(author));
        }
        OperationUtil.submitDeltas(context, waveletProvider, new LoggingRequestListener(
                org.waveprotocol.wave.util.logging.Log.get(KuneWaveServiceDefault.class)));
    }

    private void doSubmit(final SimpleArgCallback<WaveRef> onCreate, final OperationContextImpl context,
            final WaveRef wavename) {
        OperationUtil.submitDeltas(context, waveletProvider, new SubmitRequestListener() {
            @Override
            public void onFailure(final String arg0) {
                KuneWaveServiceDefault.this.onFailure("Wave creation failed, onFailure: " + arg0);
            }

            @Override
            public void onSuccess(final int arg0, final HashedVersion arg1, final long arg2) {
                LOG.info("Wave creation success: " + arg1);
                onCreate.onCallback(wavename);
            }
        });
    }

    @Override
    public Wavelet fetchWave(final WaveId waveId, final WaveletId waveletId, final String author) {
        final OperationQueue opQueue = new OperationQueue();
        opQueue.fetchWavelet(waveId, waveletId);
        Wavelet wavelet = null;
        final OperationContextImpl context = new OperationContextImpl(waveletProvider,
                converterManager.getEventDataConverter(ProtocolVersion.DEFAULT), conversationUtil);
        final OperationRequest request = opQueue.getPendingOperations().get(0);
        OperationUtil.executeOperation(request, operationRegistry, context, participantUtils.of(author));
        final String reqId = request.getId();
        final JsonRpcResponse response = context.getResponse(reqId);
        if (response != null && response.isError()) {
            final String errorMessage = processErrorMessage(context.getResponse(reqId).getErrorMessage());
            onFailure(errorMessage);
            if ("Access rejected".equals(errorMessage)) {
                throw new AccessViolationException(errorMessage);
            } else {
                throw new DefaultException(errorMessage);
            }
        } else {
            // Duplicate code from WaveService
            assert response != null;
            final WaveletData waveletData = (WaveletData) response.getData().get(ParamsProperty.WAVELET_DATA);
            final Map<String, Blip> blips = new HashMap<String, Blip>();
            final Map<String, BlipThread> threads = new HashMap<String, BlipThread>();
            wavelet = Wavelet.deserialize(opQueue, blips, threads, waveletData);

            // Deserialize threads.
            @SuppressWarnings("unchecked")
            final Map<String, BlipThread> tempThreads = (Map<String, BlipThread>) response.getData()
                    .get(ParamsProperty.THREADS);
            for (final Map.Entry<String, BlipThread> entry : tempThreads.entrySet()) {
                final BlipThread thread = entry.getValue();
                threads.put(entry.getKey(),
                        new BlipThread(thread.getId(), thread.getLocation(), thread.getBlipIds(), blips));
            }

            // Deserialize blips.
            @SuppressWarnings("unchecked")
            final Map<String, BlipData> blipDatas = (Map<String, BlipData>) response.getData()
                    .get(ParamsProperty.BLIPS);
            for (final Map.Entry<String, BlipData> entry : blipDatas.entrySet()) {
                blips.put(entry.getKey(), Blip.deserialize(opQueue, wavelet, entry.getValue()));
            }
        }
        return wavelet;
    }

    @Override
    public Wavelet fetchWave(final WaveRef waveName, final String author) {
        final WaveId waveId = waveName.getWaveId();
        final WaveletId waveletId = waveName.getWaveletId();
        return fetchWave(waveId, waveletId, author);
    }

    @Override
    public Gadget getGadget(final WaveRef waveletName, final String author, final URL gadgetUrl) {
        final Wavelet wavelet = fetchWave(waveletName, author);
        final Blip rootBlip = wavelet.getRootBlip();
        for (final Element elem : rootBlip.getElements().values()) {
            if (elem.isGadget()) {
                final Map<String, String> properties = elem.getProperties();
                if (properties.get(Gadget.URL).equals(gadgetUrl.toString())) {
                    return (Gadget) elem;
                }
            }
        }
        return null;
    }

    @Override
    public Participants getParticipants(final WaveRef waveref, final String author) {
        return fetchWave(waveref, author).getParticipants();
    }

    @Override
    public String getTitle(final WaveRef waveName, final String author) {
        final Wavelet wavelet = fetchWave(waveName, author);
        return wavelet.getTitle();
    }

    @Override
    public boolean isParticipant(final Wavelet wavelet, final String user) {
        return wavelet.getParticipants().contains(participantUtils.of(user).toString());
    }

    private void onFailure(final String message) {
        LOG.error(message);
    }

    private String processErrorMessage(final String message) {
        final String errorMsg = TextUtils.notEmpty(message) ? message : "Wave operation failed";
        return errorMsg;
    }

    @Override
    public String render(final Wavelet wavelet) {
        final ClientAction clientPage = waveRenderer.render(wavelet, 0);
        final String html = clientPage.getHtml();
        return html;
    }

    @Override
    public String render(final WaveRef waveRef, final String author) {
        return render(fetchWave(waveRef, author));
    }

    @Override
    public void setGadgetProperty(final WaveRef waveletName, final String author, final URL gadgetUrl,
            final Map<String, String> newProperties) {
        // Note: See BlipContentRefs DocumentModifyService
        final Wavelet wavelet = fetchWave(waveletName, author);
        final Blip rootBlip = wavelet.getRootBlip();
        for (final Element elem : rootBlip.getElements().values()) {
            if (elem.isGadget()) {
                final Map<String, String> properties = elem.getProperties();
                if (properties.get(Gadget.URL).equals(gadgetUrl.toString())) {
                    // This is the gadget we want to modify (the first of that type)
                    final List<Element> updatedElementsIn = Lists.newArrayListWithCapacity(1);
                    final Gadget gadget = (Gadget) elem;
                    final OperationQueue opQueue = new OperationQueue();
                    for (final String propKey : newProperties.keySet()) {
                        final String value = newProperties.get(propKey);
                        properties.put(propKey, value);
                        // properties.put(propertyNameToDelete, null);
                        gadget.setProperty(propKey, value);
                        // updatedElementsIn.add(new Gadget(properties));
                    }
                    updatedElementsIn.add(gadget);
                    final OperationRequest operationRequest = opQueue.modifyDocument(rootBlip);
                    operationRequest.addParameter(Parameter.of(ParamsProperty.MODIFY_ACTION,
                            new DocumentModifyAction(ModifyHow.UPDATE_ELEMENT, NO_VALUES, NO_ANNOTATION_KEY,
                                    updatedElementsIn, NO_BUNDLED_ANNOTATIONS, false)));
                    operationRequest.addParameter(Parameter.of(ParamsProperty.MODIFY_QUERY, new DocumentModifyQuery(
                            ElementType.GADGET, ImmutableMap.of(Gadget.URL, gadgetUrl.toString()), -1)));
                    doOperations(author, opQueue, "set gadget property");
                    break;
                }
            }
        }
    }

    @Override
    public void setTitle(final WaveRef waveName, final String title, final String author) {
        final Wavelet wavelet = fetchWave(waveName, author);
        final OperationQueue opQueue = new OperationQueue();
        opQueue.setTitleOfWavelet(wavelet, title);
        doOperations(author, opQueue, "set title");
    }

}