Java tutorial
// 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; } }