All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.collaborationengine.PresenceManager Maven / Gradle / Ivy

/*
 * Copyright 2020-2022 Vaadin Ltd.
 *
 * This program is available under Commercial Vaadin Runtime License 1.0
 * (CVRLv1).
 *
 * For the full License, see http://vaadin.com/license/cvrl-1
 */
package com.vaadin.collaborationengine;

import java.util.LinkedHashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.collaborationengine.PresenceHandler.PresenceContext;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.internal.UsageStatistics;
import com.vaadin.flow.shared.Registration;

/**
 * Manager to handle user presence in topics. It allows to set the user presence
 * in the set topic and get a list of users currently set as present in the same
 * topic. A handler can be set to react when the presence of a user changes.
 *
 * @author Vaadin Ltd
 * @since 3.2
 */
public class PresenceManager extends AbstractCollaborationManager {

    static {
        UsageStatistics.markAsUsed(
                CollaborationEngine.COLLABORATION_ENGINE_NAME
                        + "/PresenceManager",
                CollaborationEngine.COLLABORATION_ENGINE_VERSION);
    }

    static final Logger LOGGER = LoggerFactory.getLogger(PresenceManager.class);

    private static class UserEntry {
        private int count = 0;
        private Registration registration;
    }

    static final String LIST_NAME = PresenceManager.class.getName();

    private final Map userEntries = new LinkedHashMap<>();

    private ListKey ownPresenceKey;

    private CollaborationList list;

    private PresenceHandler presenceHandler;

    private boolean markAsPresent = false;

    private Registration subscribeRegistration;

    /**
     * Creates a new manager for the provided component.
     * 

* The provided user information is used to set the presence of the local * user with {@link #markAsPresent(boolean)} (the default is {@code false}). *

* * @param component * the component which holds UI access, not {@code null} * @param localUser * the information of the local user, not {@code null} * @param topicId * the id of the topic to connect to, not {@code null} */ public PresenceManager(Component component, UserInfo localUser, String topicId) { this(new ComponentConnectionContext(component), localUser, topicId, CollaborationEngine.getInstance()); } /** * Creates a new manager for the provided connection context. *

* The provided user information is used to set the presence of the local * user with {@link #markAsPresent(boolean)} (the default is {@code false}). *

* * @param context * the context that manages connection status, not {@code null} * @param localUser * the information of the local user, not {@code null} * @param topicId * the id of the topic to connect to, not {@code null} * @param collaborationEngine * the collaboration engine instance to use, not {@code null} */ public PresenceManager(ConnectionContext context, UserInfo localUser, String topicId, CollaborationEngine collaborationEngine) { super(localUser, topicId, collaborationEngine); openTopicConnection(context, this::onConnectionActivate); } /** * Configures the manager to mark the local user present in the topic. *

* If the user wasn't already present in the topic, all managers connected * to the same topic will be notified of the change and their handlers will * be applied to the user instance. * * @param markAsPresent * {@code true} to mark the user as present in the topic, * {@code false} to set as not present */ public void markAsPresent(boolean markAsPresent) { if (this.markAsPresent != markAsPresent && list != null) { if (markAsPresent) { addLocalUserToTopic(); } else { removeLocalUserFromTopic(); } } this.markAsPresent = markAsPresent; } private void addLocalUserToTopic() { assert ownPresenceKey == null; ListOperation operation = ListOperation.insertLast(getLocalUser()) .withScope(EntryScope.CONNECTION); ownPresenceKey = list.apply(operation).getKey(); } private void removeLocalUserFromTopic() { assert ownPresenceKey != null; list.set(ownPresenceKey, null); ownPresenceKey = null; } /** * Sets a handler which will be invoked when a user becomes present. *

* The handler accepts a {@link UserInfo} instance as a parameter and should * return a {@link Registration} which will be removed when the user stops * being present. *

* Replacing an existing handler will remove all registrations from the * previous one. * * @param handler * the user presence handler, or {@code null} to remove an * existing handler * @deprecated Use {@link #setPresenceHandler(PresenceHandler)} instead */ @Deprecated public void setNewUserHandler(NewUserHandler handler) { setPresenceHandler(context -> handler.handleNewUser(context.getUser())); } /** * Sets a handler which will be invoked when a user becomes present. *

* The handler accepts a {@link PresenceContext} instance as a parameter and * should return a {@link Registration} which will be removed when the user * stops being present. *

* Replacing an existing handler will remove all registrations from the * previous one. * * @param handler * the user presence handler, or {@code null} to remove an * existing handler */ public void setPresenceHandler(PresenceHandler handler) { resetEntries(); this.presenceHandler = handler; if (handler != null && list != null) { subscribeRegistration = list.subscribe(this::onListChange); } } private Registration onConnectionActivate(TopicConnection topicConnection) { list = topicConnection.getNamedList(LIST_NAME); if (markAsPresent) { addLocalUserToTopic(); } if (this.presenceHandler != null && subscribeRegistration == null) { subscribeRegistration = list.subscribe(this::onListChange); } return this::onConnectionDeactivate; } private void onConnectionDeactivate() { ownPresenceKey = null; list = null; resetEntries(); } private void onListChange(ListChangeEvent event) { switch (event.getType()) { case INSERT: handleNewUser(event.getValue(UserInfo.class)); break; case SET: if (event.getValue(UserInfo.class) == null) { handleRemovedUser(event.getOldValue(UserInfo.class)); } else { throw new UnsupportedOperationException( "Cannot update an existing entry"); } break; case MOVE: // Unexpected, but no problem in ignoring break; } } private void handleRemovedUser(UserInfo removedUser) { UserEntry userEntry = userEntries.get(removedUser.getId()); logUserOperation("remove", removedUser, userEntry != null); assert userEntry != null; if (--userEntry.count == 0) { removeRegistration(userEntry); userEntries.remove(removedUser.getId()); } } private void logUserOperation(String operation, UserInfo userInfo, boolean present) { LOGGER.debug("{}: handle {} user {} ({}): {}present", this, operation, userInfo.getName(), userInfo.getId(), !present ? "not " : ""); } private void handleNewUser(UserInfo addedUser) { UserEntry userEntry = userEntries.computeIfAbsent(addedUser.getId(), ignore -> new UserEntry()); logUserOperation("add", addedUser, userEntry.count == 0); if (userEntry.count++ == 0) { if (presenceHandler != null) { assert userEntry.registration == null; userEntry.registration = presenceHandler .handlePresence(new DefaultPresenceContext(addedUser)); } } } private void removeRegistration(UserEntry entry) { Registration registration = entry.registration; if (registration != null) { registration.remove(); entry.registration = null; } } private void resetEntries() { if (subscribeRegistration != null) { subscribeRegistration.remove(); subscribeRegistration = null; } userEntries.values().forEach(this::removeRegistration); LOGGER.debug("{}: clear user entries", this); userEntries.clear(); } static class DefaultPresenceContext implements PresenceContext { private final UserInfo user; public DefaultPresenceContext(UserInfo user) { this.user = user; } @Override public UserInfo getUser() { return user; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy