org.restcomm.media.control.mgcp.command.CreateConnectionCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.restcomm.media.control.mgcp.command.CreateConnectionCommand.java

Source

/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2016, Telestax Inc and individual contributors
 * by the @authors tag. 
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.restcomm.media.control.mgcp.command;

import org.apache.log4j.Logger;
import org.restcomm.media.control.mgcp.connection.MgcpConnection;
import org.restcomm.media.control.mgcp.connection.MgcpLocalConnection;
import org.restcomm.media.control.mgcp.endpoint.MgcpEndpoint;
import org.restcomm.media.control.mgcp.endpoint.MgcpEndpointManager;
import org.restcomm.media.control.mgcp.exception.MgcpCallNotFoundException;
import org.restcomm.media.control.mgcp.exception.MgcpConnectionException;
import org.restcomm.media.control.mgcp.exception.MgcpConnectionNotFoundException;
import org.restcomm.media.control.mgcp.exception.MgcpException;
import org.restcomm.media.control.mgcp.exception.UnrecognizedMgcpNamespaceException;
import org.restcomm.media.control.mgcp.message.LocalConnectionOptions;
import org.restcomm.media.control.mgcp.message.MgcpParameterType;
import org.restcomm.media.control.mgcp.message.MgcpResponseCode;
import org.restcomm.media.control.mgcp.util.collections.Parameters;
import org.restcomm.media.spi.ConnectionMode;

import com.google.common.base.Optional;

/**
 * This command is used to create a connection between two endpoints.
 * 
 * @author Henrique Rosa (henrique.rosa@telestax.com)
 *
 */
public class CreateConnectionCommand extends AbstractMgcpCommand {

    private static final Logger log = Logger.getLogger(CreateConnectionCommand.class);

    protected static final String WILDCARD_ALL = "*";
    protected static final String WILDCARD_ANY = "$";
    protected static final String ENDPOINT_ID_SEPARATOR = "@";

    public CreateConnectionCommand(int transactionId, Parameters<MgcpParameterType> parameters,
            MgcpEndpointManager endpointManager) {
        super(transactionId, parameters, endpointManager);
    }

    private int loadCallId(Parameters<MgcpParameterType> parameters) throws MgcpCommandException {
        // Call ID
        Optional<Integer> callId = parameters.getIntegerBase16(MgcpParameterType.CALL_ID);
        if (!callId.isPresent()) {
            throw new MgcpCommandException(MgcpResponseCode.INCORRECT_CALL_ID);
        }
        return callId.get();
    }

    private String loadEndpointId(Parameters<MgcpParameterType> parameters) throws MgcpCommandException {
        Optional<String> endpointId = parameters.getString(MgcpParameterType.ENDPOINT_ID);
        if (!endpointId.isPresent()) {
            throw new MgcpCommandException(MgcpResponseCode.ENDPOINT_UNKNOWN);
        }
        if (endpointId.get().indexOf(WILDCARD_ALL) != -1) {
            throw new MgcpCommandException(MgcpResponseCode.WILDCARD_TOO_COMPLICATED);
        }
        return endpointId.get();
    }

    private String loadSecondEndpointId(Parameters<MgcpParameterType> parameters) throws MgcpCommandException {
        Optional<String> secondEndpointId = parameters.getString(MgcpParameterType.SECOND_ENDPOINT);
        if (secondEndpointId.isPresent()) {
            if (secondEndpointId.get().indexOf(WILDCARD_ALL) != -1) {
                throw new MgcpCommandException(MgcpResponseCode.WILDCARD_TOO_COMPLICATED);
            }
        }
        return secondEndpointId.or("");
    }

    private String loadRemoteDescription(Parameters<MgcpParameterType> parameters) throws MgcpCommandException {
        Optional<String> remoteSdp = parameters.getString(MgcpParameterType.SDP);
        Optional<String> secondEndpointId = parameters.getString(MgcpParameterType.SECOND_ENDPOINT);
        if (secondEndpointId.isPresent() && remoteSdp.isPresent()) {
            throw new MgcpCommandException(MgcpResponseCode.PROTOCOL_ERROR.code(), "Z2 and SDP present in message");
        }
        return remoteSdp.or("");
    }

    private ConnectionMode loadConnectionMode(Parameters<MgcpParameterType> parameters)
            throws MgcpCommandException {
        Optional<String> mode = parameters.getString(MgcpParameterType.MODE);
        try {
            if (!mode.isPresent()) {
                throw new MgcpCommandException(MgcpResponseCode.INVALID_OR_UNSUPPORTED_MODE);
            } else {
                return ConnectionMode.fromDescription(mode.get());
            }
        } catch (IllegalArgumentException e) {
            throw new MgcpCommandException(MgcpResponseCode.INVALID_OR_UNSUPPORTED_MODE);
        }
    }

    private MgcpEndpoint retrieveEndpoint(String endpointId) throws MgcpCommandException {
        // Get local name
        final int indexOfSeparator = endpointId.indexOf(ENDPOINT_ID_SEPARATOR);
        final String localName = endpointId.substring(0, indexOfSeparator);

        final MgcpEndpoint endpoint;
        final int indexOfAll = endpointId.indexOf(WILDCARD_ANY);
        if (indexOfAll == -1) {
            // Search for registered endpoint
            endpoint = this.endpointManager.getEndpoint(endpointId);

            if (endpoint == null) {
                throw new MgcpCommandException(MgcpResponseCode.ENDPOINT_UNKNOWN);
            }
        } else {
            // Create new endpoint for a specific name space
            try {
                endpoint = this.endpointManager.registerEndpoint(localName.substring(0, indexOfAll));
            } catch (UnrecognizedMgcpNamespaceException e) {
                throw new MgcpCommandException(MgcpResponseCode.ENDPOINT_NOT_AVAILABLE);
            }
        }
        return endpoint;
    }

    /**
     * Creates a new Remote Connection.
     * 
     * <p>
     * The connection will be half-open and a Local Connection Description is generated.
     * </p>
     * 
     * @param callId The call identifies which indicates to which session the connection belongs to.
     * @param mode The connection mode.
     * @param endpoint The endpoint where the connection will be registered to.
     * 
     * @return The new connection
     * @throws MgcpConnectionException If connection could not be half opened.
     */
    private MgcpConnection createRemoteConnection(int callId, ConnectionMode mode, MgcpEndpoint endpoint,
            CrcxContext context) throws MgcpConnectionException {
        // Create connection
        MgcpConnection connection = endpoint.createConnection(callId, false);
        // TODO set call agent
        // TODO provide local connection options
        String localDescription = connection.halfOpen(new LocalConnectionOptions());
        context.setLocalDescription(localDescription);
        connection.setMode(mode);
        return connection;
    }

    /**
     * Creates a new Remote Connection.
     * 
     * <p>
     * The connection will be fully open and connected to the remote peer.<br>
     * A Local Connection Description is generated.
     * </p>
     * 
     * @param callId The the call identifies which indicates to which session the connection belongs to.
     * @param mode The connection mode.
     * @param remoteDescription The description of the remote connection.
     * @param endpoint The endpoint where the connection will be registered to.
     * 
     * @return The new connection
     * @throws MgcpConnectionException If connection could not be opened
     */
    private MgcpConnection createRemoteConnection(int callId, ConnectionMode mode, String remoteDescription,
            MgcpEndpoint endpoint, CrcxContext context) throws MgcpConnectionException {
        MgcpConnection connection = endpoint.createConnection(callId, false);
        // TODO set call agent
        String localDescription = connection.open(remoteDescription);
        context.setLocalDescription(localDescription);

        connection.setMode(mode);
        return connection;
    }

    /**
     * Creates a new Local Connection.
     * 
     * <p>
     * The connection will be fully open and connected to a secondary endpoint.<br>
     * </p>
     * 
     * @param callId The the call identifies which indicates to which session the connection belongs to.
     * @param secondEndpoint The endpoint where the connection will be registered to.
     * 
     * @return The new connection
     * @throws MgcpException If connection could not be opened.
     */
    private MgcpConnection createLocalConnection(int callId, MgcpEndpoint endpoint) throws MgcpConnectionException {
        MgcpConnection connection = endpoint.createConnection(callId, true);
        connection.open(null);
        return connection;
    }

    private void validateParameters(Parameters<MgcpParameterType> parameters, CrcxContext context)
            throws MgcpCommandException {
        context.setCallId(loadCallId(parameters));
        context.setEndpointId(loadEndpointId(parameters));
        context.setSecondEndpointId(loadSecondEndpointId(parameters));
        context.setRemoteDescription(loadRemoteDescription(parameters));
        context.setConnectionMode(loadConnectionMode(parameters));
    }

    private void executeCommand(CrcxContext context) throws MgcpCommandException, MgcpConnectionException {
        // Retrieve Endpoints
        final String endpointId = context.getEndpointId();
        final String secondEndpointId = context.getSecondEndpointId();
        final MgcpEndpoint endpoint1 = retrieveEndpoint(endpointId);
        final MgcpEndpoint endpoint2 = secondEndpointId.isEmpty() ? null : retrieveEndpoint(secondEndpointId);

        // Update context with endpoint ID (in case they new endpoints were created)
        context.setEndpointId(endpoint1.getEndpointId().toString());
        if (endpoint2 != null) {
            context.setSecondEndpointId(endpoint2.getEndpointId().toString());
        }

        // Create Connections
        if (endpoint2 == null) {
            MgcpConnection connection;
            if (context.getRemoteDescription().isEmpty()) {
                // Create half-open connection
                connection = createRemoteConnection(context.getCallId(), context.getConnectionMode(), endpoint1,
                        context);
            } else {
                // Create open connection
                connection = createRemoteConnection(context.getCallId(), context.getConnectionMode(),
                        context.getRemoteDescription(), endpoint1, context);
            }

            // Update context with identifiers of newly created connection
            context.setConnectionId(connection.getIdentifier());
        } else {
            // Create two local connections between both endpoints
            MgcpConnection connection1 = createLocalConnection(context.getCallId(), endpoint1);
            MgcpConnection connection2 = createLocalConnection(context.getCallId(), endpoint2);

            // Update context with identifiers of newly created connection
            context.setConnectionId(connection1.getIdentifier());
            context.setSecondConnectionId(connection2.getIdentifier());

            // Join connections
            ((MgcpLocalConnection) connection1).join((MgcpLocalConnection) connection2);

            // Set connection mode
            connection1.setMode(context.getConnectionMode());
            connection2.setMode(ConnectionMode.SEND_RECV);
        }
    }

    private void rollback(CrcxContext context) {
        final int callId = context.getCallId();
        final String endpointId = context.getEndpointId();
        final String secondEndpointId = context.getSecondEndpointId();
        final int connectionId = context.getConnectionId();
        final int secondConnectionId = context.getSecondConnectionId();

        // Retrieve Endpoints
        MgcpEndpoint endpoint1 = endpointId.isEmpty() ? null : this.endpointManager.getEndpoint(endpointId);
        MgcpEndpoint endpoint2 = secondEndpointId.isEmpty() ? null
                : this.endpointManager.getEndpoint(secondEndpointId);

        // Delete created endpoints
        if (endpoint1 != null && connectionId > 0) {
            try {
                endpoint1.deleteConnection(callId, connectionId);
            } catch (MgcpCallNotFoundException | MgcpConnectionNotFoundException e) {
                log.error("Could not delete primary connection. " + e.getMessage());
            }
        }

        if (endpoint2 != null && secondConnectionId > 0) {
            try {
                endpoint2.deleteConnection(callId, secondConnectionId);
            } catch (MgcpCallNotFoundException | MgcpConnectionNotFoundException e) {
                log.error("Could not delete secondary connection. " + e.getMessage());
            }
        }

    }

    private MgcpCommandResult respond(CrcxContext context) {
        Parameters<MgcpParameterType> parameters = new Parameters<>();
        MgcpCommandResult result = new MgcpCommandResult(this.transactionId, context.getCode(),
                context.getMessage(), parameters);
        boolean successful = context.getCode() < 300;

        if (successful) {
            translateContext(context, parameters);
        }
        return result;
    }

    private void translateContext(CrcxContext context, Parameters<MgcpParameterType> parameters) {
        // Primary endpoint and connection
        final String endpointId = context.getEndpointId();
        final int connectionId = context.getConnectionId();
        if (!endpointId.isEmpty() && connectionId > 0) {
            parameters.put(MgcpParameterType.ENDPOINT_ID, endpointId);
            parameters.put(MgcpParameterType.CONNECTION_ID, Integer.toHexString(connectionId));
        }

        final String secondEndpointId = context.getSecondEndpointId();
        final int secondConnectionId = context.getSecondConnectionId();
        if (!secondEndpointId.isEmpty() && secondConnectionId > 0) {
            parameters.put(MgcpParameterType.SECOND_ENDPOINT, secondEndpointId);
            parameters.put(MgcpParameterType.CONNECTION_ID2, Integer.toHexString(secondConnectionId));
        }

        final String localDescription = context.getLocalDescription();
        if (!localDescription.isEmpty()) {
            parameters.put(MgcpParameterType.SDP, localDescription);
        }
    }

    @Override
    public MgcpCommandResult call() {
        // Initialize empty context
        CrcxContext context = new CrcxContext();
        try {
            // Validate Parameters
            validateParameters(this.requestParameters, context);
            // Execute Command
            executeCommand(context);
            context.setCode(MgcpResponseCode.TRANSACTION_WAS_EXECUTED.code());
            context.setMessage(MgcpResponseCode.TRANSACTION_WAS_EXECUTED.message());
        } catch (RuntimeException | MgcpConnectionException e) {
            log.error("Unexpected error occurred during tx=" + this.transactionId + " execution. Reason: "
                    + e.getMessage() + ". Rolling back.");
            rollback(context);
            context.setCode(MgcpResponseCode.PROTOCOL_ERROR.code());
            context.setMessage(MgcpResponseCode.PROTOCOL_ERROR.message());
        } catch (MgcpCommandException e) {
            log.error("Protocol error occurred during tx=" + this.transactionId + " execution. Reason: "
                    + e.getMessage());
            context.setCode(e.getCode());
            context.setMessage(e.getMessage());
        }
        return respond(context);
    }

}