org.axonframework.commandhandling.distributed.websockets.WebsocketCommandBusConnectorClient.java Source code

Java tutorial

Introduction

Here is the source code for org.axonframework.commandhandling.distributed.websockets.WebsocketCommandBusConnectorClient.java

Source

/*
 * Copyright (c) 2010-2016. Axon Framework
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.axonframework.commandhandling.distributed.websockets;

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.GenericObjectPoolFactory;
import org.axonframework.commandhandling.CommandCallback;
import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.distributed.CommandBusConnectorCommunicationException;
import org.axonframework.commandhandling.distributed.CommandCallbackRepository;
import org.axonframework.commandhandling.distributed.CommandCallbackWrapper;
import org.axonframework.serialization.Serializer;
import org.axonframework.serialization.SimpleSerializedObject;
import org.axonframework.serialization.xml.XStreamSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.*;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * The client to connect to the Websocket server. It is capable of scaling up the amount of clients in a pool if a
 * single connection is not sufficient for the load applied.
 *
 * @author Koen Lavooij
 */
@ClientEndpoint
public class WebsocketCommandBusConnectorClient extends Endpoint
        implements MessageHandler.Whole<ByteBuffer>, Closeable {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebsocketCommandBusConnectorClient.class);
    private static final int DEFAULT_SESSION_COUNT = 10;

    private final Serializer serializer = new XStreamSerializer();
    private final CommandCallbackRepository<String> repository = new CommandCallbackRepository<>();
    private final ObjectPool<Session> sessions;

    public WebsocketCommandBusConnectorClient(ClientSessionFactory clientSessionFactory) {
        this(clientSessionFactory, DEFAULT_SESSION_COUNT);
    }

    public WebsocketCommandBusConnectorClient(ClientSessionFactory clientSessionFactory, int sessionCount) {
        //Create the pool. Server side connections are bound to the commands in process. Therefore scaling down the
        //amount of connections results in losing callbacks of pending commands. We will therefore never scale down or
        //invalidate connections.
        GenericObjectPool.Config config = new GenericObjectPool.Config();
        config.maxActive = sessionCount;
        config.maxIdle = sessionCount;
        config.maxWait = -1;
        config.minEvictableIdleTimeMillis = -1;
        config.minIdle = 0;
        config.numTestsPerEvictionRun = 0;
        config.softMinEvictableIdleTimeMillis = -1;
        config.testOnBorrow = true;
        config.testOnReturn = false;
        config.testWhileIdle = false;
        config.timeBetweenEvictionRunsMillis = -1;
        config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;

        sessions = new GenericObjectPoolFactory<>(new PoolableObjectFactory<Session>() {
            @Override
            public Session makeObject() throws Exception {
                return clientSessionFactory.createSession(WebsocketCommandBusConnectorClient.this);
            }

            @Override
            public void destroyObject(Session obj) throws Exception {
                if (obj.isOpen())
                    obj.close();
            }

            @Override
            public boolean validateObject(Session obj) {
                return obj.isOpen();
            }

            @Override
            public void activateObject(Session obj) throws Exception {
                //
            }

            @Override
            public void passivateObject(Session obj) throws Exception {
                //
            }
        }, config).createPool();
    }

    @Override
    public void onOpen(Session session, EndpointConfig config) {
        //log here?
    }

    @Override
    public void onError(Session session, Throwable cause) {
        LOGGER.warn("Connection error on session " + session.getId(), cause);
    }

    @Override
    public void onClose(Session session, CloseReason closeReason) {
        if (closeReason.getCloseCode() != CloseReason.CloseCodes.NORMAL_CLOSURE) {
            LOGGER.warn("Session closed because " + closeReason.getReasonPhrase());
        }
        try {
            sessions.invalidateObject(session);
            repository.cancelCallbacks(session.getId());
        } catch (Exception e) {
            LOGGER.error("Could not invalidate session", e);
        }
    }

    @SuppressWarnings("unchecked")
    public void onMessage(ByteBuffer data) {
        //deserialize a message
        WebsocketResultMessage message = serializer.deserialize(new SimpleSerializedObject<>(data.array(),
                byte[].class, serializer.typeForClass(WebsocketResultMessage.class)));

        //get the waiting callback
        CommandCallbackWrapper callbackWrapper = repository.fetchAndRemove(message.getCommandId());
        if (callbackWrapper != null) {
            if (message.getCause() != null) {
                callbackWrapper.fail(message.getCause());
            } else {
                callbackWrapper.success(message.getResult());
            }
        } else {
            LOGGER.error("Did not find callback for ID " + message.getCommandId());
        }
    }

    /**
     * Sends a command to the servewr
     * @param command The command to send
     * @param callback The callback to send Command results to. May be null if no callback is required
     * @param <C> The type of the Command
     * @param <R> The type of the Command result.
     */
    public <C, R> void send(CommandMessage<C> command, CommandCallback<? super C, R> callback) {
        ByteBuffer data = ByteBuffer.wrap(serializer
                .serialize(new WebsocketCommandMessage<>(command, callback != null), byte[].class).getData());

        try {
            Session session = sessions.borrowObject();
            try {
                LOGGER.debug("Using session " + session.getId() + " to send " + command.getCommandName());
                if (callback != null) {
                    repository.store(command.getIdentifier(),
                            new CommandCallbackWrapper<>(session.getId(), command, callback));
                }
                session.getBasicRemote().sendBinary(data);
            } finally {
                sessions.returnObject(session);
            }
        } catch (Exception e) {
            LOGGER.error("Error sending command", e);
            if (callback != null) {
                callback.onFailure(command, new CommandBusConnectorCommunicationException(
                        "Failed to send command of type " + command.getCommandName() + " to remote", e));
            }
        }
    }

    @Override
    public void close() throws IOException {
        try {
            sessions.close();
        } catch (Exception e) {
            throw new IOException(e);
        }
    }
}