io.v.chat.ChatChannel.java Source code

Java tutorial

Introduction

Here is the source code for io.v.chat.ChatChannel.java

Source

// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package io.v.chat;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;

import org.joda.time.Duration;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.v.v23.V;
import io.v.v23.context.VContext;
import io.v.v23.namespace.Namespace;
import io.v.v23.naming.GlobReply;
import io.v.v23.naming.MountEntry;
import io.v.v23.naming.MountedServer;
import io.v.v23.rpc.Server;
import io.v.v23.rpc.ServerCall;
import io.v.v23.security.BlessingPattern;
import io.v.v23.security.Blessings;
import io.v.v23.security.VPrincipal;
import io.v.v23.security.VSecurity;
import io.v.v23.security.access.AccessList;
import io.v.v23.security.access.Permissions;
import io.v.v23.services.mounttable.Constants;
import io.v.v23.verror.VException;
import io.v.x.chat.vdl.ChatServer;

public class ChatChannel {
    private final String name;
    private VContext ctx;
    private final ChatChannelListener listener;

    private static final int MAX_NAME_RETRIES = 25;
    private static final Logger logger = Logger.getLogger(ChatChannel.class.getName());

    private Server server;

    public ChatChannel(ScheduledExecutorService service, VContext ctx, String name, ChatChannelListener listener) {
        this.ctx = ctx;
        this.name = name;
        this.listener = listener;

        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    doUpdateParticipants();
                } catch (VException e) {
                    System.err.println("Could not update the list of chat participants");
                    e.printStackTrace();
                }
            }
        }, 0, 5, TimeUnit.SECONDS);
    }

    public void join() throws VException {
        final ChatServer chatServer = new ChatServer() {
            @Override
            public void sendMessage(VContext ctx, ServerCall call, String text) throws VException {
                String[] blessingNames = VSecurity.getRemoteBlessingNames(ctx, call.security());
                String whom;
                if (blessingNames == null || blessingNames.length == 0) {
                    whom = "unknown";
                } else {
                    whom = blessingUsername(blessingNames[0]);
                }

                listener.messageReceived(whom, text);
            }
        };

        String mountPath = getLockedPath(ctx);
        if (mountPath != null) {
            ctx = V.withNewServer(ctx, mountPath, chatServer, VSecurity.newAllowEveryoneAuthorizer());
        } else {
            throw new VException("Could not find an appropriate path name for the chat server");
        }
    }

    /**
     * Returns a path to which a chat server may be bound, or {@code null} if no appropriate path
     * could be found.
     */
    private String getLockedPath(VContext ctx) {
        VPrincipal principal = V.getPrincipal(ctx);
        Blessings defaultBlessings = principal.blessingStore().defaultBlessings();
        List<BlessingPattern> patterns = new ArrayList<>();
        for (String blessing : VSecurity.getBlessingNames(principal, defaultBlessings)) {
            patterns.add(new BlessingPattern(blessing));
        }
        AccessList myAcl = new AccessList(patterns, ImmutableList.<String>of());
        AccessList openAcl = new AccessList(ImmutableList.of(new BlessingPattern("...")),
                ImmutableList.<String>of());
        Permissions perms = new Permissions();
        perms.put(Constants.ADMIN.getValue(), myAcl);
        perms.put(Constants.CREATE.getValue(), myAcl);
        perms.put(Constants.MOUNT.getValue(), myAcl);

        // Let anybody read and resolve the name.
        perms.put(Constants.RESOLVE.getValue(), openAcl);
        perms.put(Constants.READ.getValue(), openAcl);

        for (int i = 0; i < MAX_NAME_RETRIES; i++) {
            String path = name + "/" + UUID.randomUUID().toString();
            try {
                V.getNamespace(ctx).setPermissions(ctx, path, perms, "");
                return path;
            } catch (VException e) {
                logger.log(Level.WARNING, "retry #" + (i + 1) + " failed", e);
                // It failed, try another name!
            }
        }
        return null;
    }

    public void leave() throws VException {
        Preconditions.checkState(server != null, "can't leave a channel you haven't joined");
        server.stop();
    }

    public void sendMessage(String s) throws VException {
        VContext context = ctx.withTimeout(new Duration(3000));
        for (Participant participant : getParticipants()) {
            try {
                participant.sendMessage(context, s);
            } catch (VException e) {
                System.err.println("Could not send message to " + participant);
                e.printStackTrace();
            }
        }
    }

    private void doUpdateParticipants() throws VException {
        listener.participantsUpdated(getParticipants());
    }

    public List<Participant> getParticipants() throws VException {
        List<Participant> participants = new ArrayList<>();
        VContext context = ctx.withTimeout(new Duration(3000));
        Namespace namespace = V.getNamespace(context);
        for (GlobReply reply : namespace.glob(context, name + "/*")) {
            if (reply instanceof GlobReply.Entry) {
                MountEntry entry = ((GlobReply.Entry) reply).getElem();
                for (MountedServer server : entry.getServers()) {
                    participants.add(new Participant(endpointUsername(server.getServer()), server.getServer()));
                    break;
                }
            }
        }
        return participants;
    }

    private static String blessingUsername(String blessingName) {
        for (String field : Splitter.on("/").split(blessingName)) {
            if (field.contains("@")) {
                return field;
            }
        }
        return blessingName;
    }

    private static String endpointUsername(String endpoint) {
        // TODO(sjr): replace this with Endpoint once that is written.
        Matcher matcher = Pattern.compile("@(.*)@@$").matcher(endpoint);
        if (!matcher.matches()) {
            return endpoint;
        }
        List<String> fields = Splitter.on('@').splitToList(matcher.group(1));
        if (fields.size() < 5) {
            return endpoint;
        }
        String joinedBlessings = Joiner.on('@').join(fields.subList(5, fields.size()));
        for (String blessing : Splitter.on('/').split(joinedBlessings)) {
            if (blessing.contains("@")) {
                return blessing;
            }
        }

        return endpoint;
    }

    @Override
    public String toString() {
        return name;
    }
}