Search in sources :

Example 6 with UserAtom

use of won.owner.model.UserAtom in project webofneeds by researchstudio-sat.

the class WonWebSocketHandler method afterConnectionClosed.

@Override
public void afterConnectionClosed(final WebSocketSession session, final CloseStatus status) throws Exception {
    super.afterConnectionClosed(session, status);
    User user = getUserForSession(session);
    if (user != null) {
        logger.debug("session closed, removing session bindings to user {}", user.getId());
        this.webSocketSessionService.removeMapping(user, session);
        for (UserAtom userAtom : user.getUserAtoms()) {
            logger.debug("removing session bindings to atom {}", userAtom.getUri());
            this.webSocketSessionService.removeMapping(userAtom.getUri(), session);
        }
    } else {
        logger.debug("connection closed, but no user found in session, no bindings removed");
    }
}
Also used : User(won.owner.model.User) UserAtom(won.owner.model.UserAtom)

Example 7 with UserAtom

use of won.owner.model.UserAtom in project webofneeds by researchstudio-sat.

the class OwnerPersistenceTest method test_delete_UserAtom.

@Test
public void test_delete_UserAtom() throws Exception {
    URI atomUri = URI.create("some:/atom.uri");
    String email = "user@example.com";
    createUserWithAtom(atomUri, email);
    Thread t1 = new Thread(() -> helper.doInSeparateTransaction(() -> createUserWithAtom(atomUri, email)));
    Thread t2 = new Thread(() -> helper.doInSeparateTransaction(() -> {
        User sameUser = userRepository.findByUsername(email);
        UserAtom sameAtom = userAtomRepository.findByAtomUri(atomUri);
        sameUser.removeUserAtom(sameAtom);
        userAtomRepository.delete(sameAtom);
    }));
    t1.start();
    t1.join();
    t2.start();
    t2.join();
}
Also used : User(won.owner.model.User) UserAtom(won.owner.model.UserAtom) URI(java.net.URI) Test(org.junit.Test)

Example 8 with UserAtom

use of won.owner.model.UserAtom in project webofneeds by researchstudio-sat.

the class WonWebSocketHandler method notifyPerPush.

private void notifyPerPush(final User user, final URI atomUri, final WonMessage wonMessage, URI connectionUri) {
    if (wonMessage.getFocalMessage().getMessageType().isResponseMessage()) {
        // we assume that this message, coming from the server here, can only be an
        // echoed message. don't send by email.
        logger.debug("not sending notification to user: message {} looks like an echo from the server", wonMessage.getMessageURI());
        return;
    }
    if (user == null) {
        logger.info("not sending notification to user: user not specified");
        return;
    }
    UserAtom userAtom = getAtomOfUser(user, atomUri);
    if (userAtom == null) {
        logger.debug("not sending notification to user: atom uri not specified");
        return;
    }
    String textMsg = WonRdfUtils.MessageUtils.getTextMessage(wonMessage);
    String iconUrl = uriService.getOwnerProtocolOwnerURI().toString() + "/skin/current/images/logo.png";
    switch(wonMessage.getMessageType()) {
        case CONNECTION_MESSAGE:
            if (userAtom.isConversations()) {
                ObjectMapper mapper = new ObjectMapper();
                ObjectNode rootNode = mapper.createObjectNode();
                rootNode.put("type", "MESSAGE");
                rootNode.put("atomUri", userAtom.getUri().toString());
                rootNode.put("connectionUri", connectionUri.toString());
                rootNode.put("icon", iconUrl);
                if (textMsg != null) {
                    rootNode.put("message", StringUtils.abbreviate(textMsg, 50));
                }
                String stringifiedJson;
                try {
                    stringifiedJson = mapper.writer().writeValueAsString(rootNode);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                pushSender.sendNotification(user, stringifiedJson);
            }
            return;
        case SOCKET_HINT_MESSAGE:
            if (userAtom.isMatches()) {
                if (!isConnectionInSuggestedState(connectionUri)) {
                    // found the connection previously and we don't want to notify them
                    return;
                }
                ObjectMapper mapper = new ObjectMapper();
                ObjectNode rootNode = mapper.createObjectNode();
                rootNode.put("type", "HINT");
                rootNode.put("atomUri", userAtom.getUri().toString());
                rootNode.put("connectionUri", connectionUri.toString());
                rootNode.put("icon", iconUrl);
                String stringifiedJson;
                try {
                    stringifiedJson = mapper.writer().writeValueAsString(rootNode);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                pushSender.sendNotification(user, stringifiedJson);
            }
            return;
        case CONNECT:
            if (userAtom.isRequests()) {
                ObjectMapper mapper = new ObjectMapper();
                ObjectNode rootNode = mapper.createObjectNode();
                rootNode.put("type", "CONNECT");
                rootNode.put("atomUri", userAtom.getUri().toString());
                rootNode.put("connectionUri", connectionUri.toString());
                rootNode.put("icon", iconUrl);
                if (textMsg != null) {
                    rootNode.put("message", StringUtils.abbreviate(textMsg, 50));
                }
                String stringifiedJson;
                try {
                    stringifiedJson = mapper.writer().writeValueAsString(rootNode);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                pushSender.sendNotification(user, stringifiedJson);
            }
            return;
        default:
            return;
    }
}
Also used : UserAtom(won.owner.model.UserAtom) ObjectNode(org.codehaus.jackson.node.ObjectNode) UncheckedIOException(java.io.UncheckedIOException) UncheckedIOException(java.io.UncheckedIOException) IOException(java.io.IOException) ObjectMapper(org.codehaus.jackson.map.ObjectMapper)

Example 9 with UserAtom

use of won.owner.model.UserAtom in project webofneeds by researchstudio-sat.

the class WonWebSocketHandler method process.

/**
 * Sends a message coming from the WoN node to the client.
 */
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public WonMessage process(final WonMessage wonMessage) {
    try {
        logger.debug("processing message {} incoming from node", wonMessage.getMessageURI());
        String wonMessageJsonLdString = WonMessageEncoder.encodeAsJsonLd(wonMessage);
        WebSocketMessage<String> webSocketMessage = new TextMessage(wonMessageJsonLdString);
        logger.debug("determining which owned atom is to be informed of message {} ", wonMessage.getMessageURI());
        URI atomUri = getOwnedAtomURIForMessageFromNode(wonMessage);
        logger.debug("obtaining WebSocketSessions for message {} ", wonMessage.getMessageURI());
        Set<WebSocketSession> webSocketSessions = webSocketSessionService.getWebSocketSessions(atomUri);
        Optional<User> userOpt = webSocketSessions == null ? Optional.empty() : webSocketSessions.stream().filter(s -> s.isOpen()).findFirst().map(s -> getUserForSession(s));
        logger.debug("found {} sessions for message {} ", webSocketSessions.size(), wonMessage.getMessageURI());
        logger.debug("found user for message {} via session: {} ", wonMessage.getMessageURI(), userOpt.isPresent());
        if (!userOpt.isPresent()) {
            userOpt = Optional.ofNullable(userRepository.findByAtomUri(atomUri));
        }
        logger.debug("found user for message {} atom uri: {} ", wonMessage.getMessageURI(), userOpt.isPresent());
        // it's quite possible that we don't find the user object this way.
        User user = userOpt.orElse(null);
        // Methods below can handle that.
        logger.debug("updating user-atom association for message {}, user has been found:{} ", wonMessage.getMessageURI(), userOpt.isPresent());
        userAtomService.updateUserAtomAssociation(wonMessage, user);
        logger.debug("trying to find WebSocketSessions for message{}, atom {}, user has been found:{}", new Object[] { wonMessage.getMessageURI(), atomUri, userOpt.isPresent() });
        webSocketSessions = webSocketSessionService.findWebSocketSessionsForAtomAndUser(atomUri, user);
        // check if we can deliver the message. If not, send email.
        if (webSocketSessions.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug("cannot deliver message {}: no websocket session found. Trying to send message by email.", wonMessage.toShortStringForDebug());
            }
            notifyUserOnDifferentChannel(wonMessage, atomUri, user);
            return wonMessage;
        }
        // we can send it - pre-cache the delivery chain:
        logger.debug("put message {} into cache before sending on websocket", wonMessage.getMessageURI());
        eagerlyCachePopulatingProcessor.process(wonMessage);
        // send to owner webapp
        int successfullySent = 0;
        for (WebSocketSession session : webSocketSessions) {
            logger.debug("sending message {} via websocket session", wonMessage.getMessageURI());
            successfullySent += sendMessageForSession(wonMessage, webSocketMessage, session, atomUri, user) ? 1 : 0;
        }
        logger.debug("sent message {} via {} websocket sessions", wonMessage.getMessageURI(), successfullySent);
        if (successfullySent == 0) {
            // we did not manage to send the message via the websocket, send it by email.
            if (logger.isDebugEnabled()) {
                logger.debug("cannot deliver message {}: none of the associated websocket sessions worked. Trying to send message by webpush and email.", wonMessage.toShortStringForDebug());
            }
            // TODO: ideally in this case
            // 1. collect multiple events occurring in close succession
            // 2. try to push
            // 3. email only if push was not successful
            notifyUserOnDifferentChannel(wonMessage, atomUri, user);
        } else {
            // Always send possible pushNotifications:
            // - maybe session is active -> message was send, but Tab is not focused
            // - Browser is running in background -> user needs to get push notification
            Optional<URI> connectionURI = WonLinkedDataUtils.getConnectionURIForIncomingMessage(wonMessage, linkedDataSource);
            if (connectionURI.isPresent()) {
                logger.debug("notifying user per web push for message {}", wonMessage.getMessageURI());
                UserAtom userAtom = getAtomOfUser(user, atomUri);
                if (userAtom == null) {
                    userOpt = Optional.ofNullable(userRepository.findByAtomUri(atomUri));
                    user = userOpt.orElse(null);
                }
                notifyPerPush(user, atomUri, wonMessage, connectionURI.get());
            }
            logger.debug("cannot notify user: cannot determine connection URI");
        }
        return wonMessage;
    } finally {
        // in any case, let the serversideactionservice do its work, if there is any to
        // do:
        logger.debug("processing server side actions for message {} if any are registered", wonMessage.getMessageURI());
        serverSideActionService.process(wonMessage);
    }
}
Also used : StringUtils(org.apache.commons.lang.StringUtils) OwnerApplicationService(won.owner.service.impl.OwnerApplicationService) LoggerFactory(org.slf4j.LoggerFactory) SessionRepository(org.springframework.session.SessionRepository) Autowired(org.springframework.beans.factory.annotation.Autowired) UserRepository(won.owner.repository.UserRepository) CloseStatus(org.springframework.web.socket.CloseStatus) LoggingUtils(won.protocol.util.LoggingUtils) TextMessage(org.springframework.web.socket.TextMessage) Duration(java.time.Duration) Map(java.util.Map) URI(java.net.URI) WonMessageEncoder(won.protocol.message.WonMessageEncoder) WonLinkedDataUtils(won.protocol.util.linkeddata.WonLinkedDataUtils) WonMessageType(won.protocol.message.WonMessageType) MethodHandles(java.lang.invoke.MethodHandles) Set(java.util.Set) Collectors(java.util.stream.Collectors) StandardCharsets(java.nio.charset.StandardCharsets) WonRdfUtils(won.protocol.util.WonRdfUtils) UncheckedIOException(java.io.UncheckedIOException) Base64(java.util.Base64) Principal(java.security.Principal) DisposableBean(org.springframework.beans.factory.DisposableBean) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) ServerSideActionService(won.owner.web.service.ServerSideActionService) Optional(java.util.Optional) MailException(org.springframework.mail.MailException) Authentication(org.springframework.security.core.Authentication) Isolation(org.springframework.transaction.annotation.Isolation) LinkedDataSource(won.protocol.util.linkeddata.LinkedDataSource) UserAtom(won.owner.model.UserAtom) AuthenticationThreadLocal(won.protocol.util.AuthenticationThreadLocal) ConnectionState(won.protocol.model.ConnectionState) MessageDigest(java.security.MessageDigest) WonMessageDecoder(won.protocol.message.WonMessageDecoder) WebSocketSession(org.springframework.web.socket.WebSocketSession) InitializingBean(org.springframework.beans.factory.InitializingBean) WonMessage(won.protocol.message.WonMessage) MessageFormat(java.text.MessageFormat) TextWebSocketHandler(org.springframework.web.socket.handler.TextWebSocketHandler) Propagation(org.springframework.transaction.annotation.Propagation) WonMessageProcessor(won.protocol.message.processor.WonMessageProcessor) ObjectNode(org.codehaus.jackson.node.ObjectNode) WonOwnerPushSender(won.owner.web.WonOwnerPushSender) Order(org.springframework.core.annotation.Order) Logger(org.slf4j.Logger) WebSocketMessage(org.springframework.web.socket.WebSocketMessage) WonOwnerMailSender(won.owner.web.WonOwnerMailSender) Session(org.springframework.session.Session) IOException(java.io.IOException) KeystoreEnabledUserDetails(won.owner.service.impl.KeystoreEnabledUserDetails) User(won.owner.model.User) URIService(won.owner.service.impl.URIService) BatchingConsumer(won.utils.batch.BatchingConsumer) UserAtomService(won.owner.web.service.UserAtomService) ObjectMapper(org.codehaus.jackson.map.ObjectMapper) Transactional(org.springframework.transaction.annotation.Transactional) User(won.owner.model.User) UserAtom(won.owner.model.UserAtom) URI(java.net.URI) TextMessage(org.springframework.web.socket.TextMessage) WebSocketSession(org.springframework.web.socket.WebSocketSession) Transactional(org.springframework.transaction.annotation.Transactional)

Example 10 with UserAtom

use of won.owner.model.UserAtom in project webofneeds by researchstudio-sat.

the class WonWebSocketHandler method notifyPerEmail.

private void notifyPerEmail(final User user, final URI atomUri, final WonMessage wonMessage, URI connectionUri) {
    if (wonMessage.getFocalMessage().getMessageType().isResponseMessage()) {
        // we assume that this message, coming from the server here, can only be an
        // echoed message. don't send by email.
        logger.debug("not sending email to user: message {} looks like an echo from the server", wonMessage.getMessageURI());
        return;
    }
    if (user == null) {
        logger.info("not sending email to user: user not specified");
        return;
    }
    if (user.isAnonymous()) {
        logger.debug("not sending email to user: user is anonymous");
        return;
    }
    if (!user.isEmailVerified()) {
        logger.debug("not sending email to user: email address not yet verified");
        return;
    }
    UserAtom userAtom = getAtomOfUser(user, atomUri);
    if (userAtom == null) {
        logger.debug("not sending email to user: atom uri not specified");
        return;
    }
    UserAtom senderAtom = getAtomOfUser(user, wonMessage.getSenderAtomURI());
    if (senderAtom != null) {
        logger.debug("not sending email to user: sender and recipient atoms are controlled by same user.");
        return;
    }
    String textMsg = WonRdfUtils.MessageUtils.getTextMessage(wonMessage);
    try {
        switch(wonMessage.getMessageType()) {
            case CONNECTION_MESSAGE:
                if (userAtom.isConversations()) {
                    emailSender.sendConversationNotificationMessage(user.getEmail(), atomUri.toString(), wonMessage.getSenderAtomURI().toString(), connectionUri.toString(), wonMessage.getRecipientSocketURIRequired().toString(), wonMessage.getSenderSocketURIRequired().toString(), textMsg);
                }
                return;
            case CONNECT:
                if (userAtom.isRequests()) {
                    emailSender.sendConnectNotificationMessage(user.getEmail(), atomUri.toString(), wonMessage.getSenderAtomURI().toString(), connectionUri.toString(), wonMessage.getRecipientSocketURIRequired().toString(), wonMessage.getSenderSocketURIRequired().toString(), textMsg);
                }
                return;
            case ATOM_HINT_MESSAGE:
            case SOCKET_HINT_MESSAGE:
                if (userAtom.isMatches()) {
                    Optional<URI> targetAtomUri = WonLinkedDataUtils.getAtomOfSocket(wonMessage.getHintTargetSocketURI(), linkedDataSource);
                    if (!isConnectionInSuggestedState(connectionUri)) {
                        // found the connection previously and we don't want to notify them
                        return;
                    }
                    if (targetAtomUri.isPresent()) {
                        // user a hash of the user's email address for the key, so as not to hold
                        // users emails in memory all the time
                        MessageDigest digest = MessageDigest.getInstance("SHA-256");
                        byte[] hash = digest.digest(user.getEmail().getBytes(StandardCharsets.UTF_8));
                        String key = "HINT" + Base64.getEncoder().encodeToString(hash);
                        String[] args = new String[] { user.getEmail(), atomUri.toString(), targetAtomUri.get().toString(), connectionUri.toString() };
                        // only count 1 item per atom/atom combination per batch key.
                        String deduplicationKey = atomUri.toString() + targetAtomUri.toString();
                        // set the configuration
                        BatchingConsumer.Config config = new BatchingConsumer.ConfigBuilder().consumeFirst(// send the first mail immediately
                        true).maxBatchAge(// empty batch at least once a day
                        Duration.ofHours(24)).maxItemInterval(// wait 10 minutes after the last
                        Duration.ofMinutes(10)).minChunkInterval(// send at most 1 mail every 6 hours
                        Duration.ofHours(6)).maxBatchSize(// as soon as we reach 50 hints, send mail
                        50).build();
                        batchingConsumer.accept(key, args, deduplicationKey, batch -> {
                            if (batch.size() == 1) {
                                String[] a = batch.iterator().next();
                                emailSender.sendHintNotificationMessage(a[0], a[1], a[2], a[3]);
                            } else if (batch.size() > 0) {
                                String[] a = batch.iterator().next();
                                Map<String, Long> hintCounts = batch.stream().collect(Collectors.groupingBy(item -> item[1], Collectors.counting()));
                                emailSender.sendMultipleHintsNotificationMessage(a[0], hintCounts);
                            }
                        }, config);
                    } else {
                        logger.info("received socket hint to {} but could not identify corresponding atom - no mail sent.", wonMessage.getHintTargetSocketURI());
                    }
                }
                return;
            case CLOSE:
                // do not send emails for a close
                return;
            case DEACTIVATE:
                // a deactivate message, coming from the WoN node. Always deliverd by email.
                emailSender.sendSystemDeactivateNotificationMessage(user.getEmail(), atomUri.toString(), textMsg);
                return;
            case ATOM_MESSAGE:
                // an atom message, coming from the WoN node. Always deliverd by email.
                emailSender.sendAtomMessageNotificationMessage(user.getEmail(), atomUri.toString(), textMsg);
                return;
            default:
                return;
        }
    } catch (MailException | NoSuchAlgorithmException ex) {
        logger.error("Email could not be sent", ex);
    }
}
Also used : BatchingConsumer(won.utils.batch.BatchingConsumer) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) URI(java.net.URI) UserAtom(won.owner.model.UserAtom) MailException(org.springframework.mail.MailException) MessageDigest(java.security.MessageDigest) Map(java.util.Map)

Aggregations

UserAtom (won.owner.model.UserAtom)15 URI (java.net.URI)9 User (won.owner.model.User)8 StringUtils (org.apache.commons.lang.StringUtils)3 Autowired (org.springframework.beans.factory.annotation.Autowired)3 Transactional (org.springframework.transaction.annotation.Transactional)3 RequestMapping (org.springframework.web.bind.annotation.RequestMapping)3 IOException (java.io.IOException)2 UncheckedIOException (java.io.UncheckedIOException)2 MethodHandles (java.lang.invoke.MethodHandles)2 URISyntaxException (java.net.URISyntaxException)2 MessageDigest (java.security.MessageDigest)2 NoSuchAlgorithmException (java.security.NoSuchAlgorithmException)2 Map (java.util.Map)2 Set (java.util.Set)2 Collectors (java.util.stream.Collectors)2 ObjectMapper (org.codehaus.jackson.map.ObjectMapper)2 ObjectNode (org.codehaus.jackson.node.ObjectNode)2 Logger (org.slf4j.Logger)2 LoggerFactory (org.slf4j.LoggerFactory)2