/* * Copyright (C) 2010 Teleal GmbH, Switzerland * * This program 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 3 of * the License, or (at your option) any later version. * * This program 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 program. If not, see <http://www.gnu.org/licenses/>. */ package org.teleal.cling.support.connectionmanager; import org.teleal.cling.binding.annotations.UpnpAction; import org.teleal.cling.binding.annotations.UpnpInputArgument; import org.teleal.cling.binding.annotations.UpnpOutputArgument; import org.teleal.cling.controlpoint.ControlPoint; import org.teleal.cling.model.ServiceReference; import org.teleal.cling.model.action.ActionException; import org.teleal.cling.model.action.ActionInvocation; import org.teleal.cling.model.message.UpnpResponse; import org.teleal.cling.model.meta.Service; import org.teleal.cling.model.types.ErrorCode; import org.teleal.cling.model.types.UnsignedIntegerFourBytes; import org.teleal.cling.model.types.csv.CSV; import org.teleal.cling.support.connectionmanager.callback.ConnectionComplete; import org.teleal.cling.support.connectionmanager.callback.PrepareForConnection; import org.teleal.cling.support.model.ConnectionInfo; import org.teleal.cling.support.model.ProtocolInfo; import org.teleal.cling.support.model.ProtocolInfos; import java.beans.PropertyChangeSupport; import java.util.logging.Logger; /** * Support for setup and teardown of an arbitrary number of connections with a manager peer. * * @author Christian Bauer * @author Alessio Gaeta */ public abstract class AbstractPeeringConnectionManagerService extends ConnectionManagerService { final private static Logger log = Logger.getLogger(AbstractPeeringConnectionManagerService.class.getName()); protected AbstractPeeringConnectionManagerService(ConnectionInfo... activeConnections) { super(activeConnections); } protected AbstractPeeringConnectionManagerService(ProtocolInfos sourceProtocolInfo, ProtocolInfos sinkProtocolInfo, ConnectionInfo... activeConnections) { super(sourceProtocolInfo, sinkProtocolInfo, activeConnections); } protected AbstractPeeringConnectionManagerService(PropertyChangeSupport propertyChangeSupport, ProtocolInfos sourceProtocolInfo, ProtocolInfos sinkProtocolInfo, ConnectionInfo... activeConnections) { super(propertyChangeSupport, sourceProtocolInfo, sinkProtocolInfo, activeConnections); } synchronized protected int getNewConnectionId() { int currentHighestID = -1; for (Integer key : activeConnections.keySet()) { if (key > currentHighestID) currentHighestID = key; } return ++currentHighestID; } synchronized protected void storeConnection(ConnectionInfo info) { CSV<UnsignedIntegerFourBytes> oldConnectionIDs = getCurrentConnectionIDs(); activeConnections.put(info.getConnectionID(), info); log.fine("Connection stored, firing event: " + info.getConnectionID()); CSV<UnsignedIntegerFourBytes> newConnectionIDs = getCurrentConnectionIDs(); getPropertyChangeSupport().firePropertyChange("CurrentConnectionIDs", oldConnectionIDs, newConnectionIDs); } synchronized protected void removeConnection(int connectionID) { CSV<UnsignedIntegerFourBytes> oldConnectionIDs = getCurrentConnectionIDs(); activeConnections.remove(connectionID); log.fine("Connection removed, firing event: " + connectionID); CSV<UnsignedIntegerFourBytes> newConnectionIDs = getCurrentConnectionIDs(); getPropertyChangeSupport().firePropertyChange("CurrentConnectionIDs", oldConnectionIDs, newConnectionIDs); } @UpnpAction(out = { @UpnpOutputArgument(name = "ConnectionID", stateVariable = "A_ARG_TYPE_ConnectionID", getterName = "getConnectionID"), @UpnpOutputArgument(name = "AVTransportID", stateVariable = "A_ARG_TYPE_AVTransportID", getterName = "getAvTransportID"), @UpnpOutputArgument(name = "RcsID", stateVariable = "A_ARG_TYPE_RcsID", getterName = "getRcsID") }) synchronized public ConnectionInfo prepareForConnection( @UpnpInputArgument(name = "RemoteProtocolInfo", stateVariable = "A_ARG_TYPE_ProtocolInfo") ProtocolInfo remoteProtocolInfo, @UpnpInputArgument(name = "PeerConnectionManager", stateVariable = "A_ARG_TYPE_ConnectionManager") ServiceReference peerConnectionManager, @UpnpInputArgument(name = "PeerConnectionID", stateVariable = "A_ARG_TYPE_ConnectionID") int peerConnectionId, @UpnpInputArgument(name = "Direction", stateVariable = "A_ARG_TYPE_Direction") String direction) throws ActionException { int connectionId = getNewConnectionId(); ConnectionInfo.Direction dir; try { dir = ConnectionInfo.Direction.valueOf(direction); } catch (Exception ex) { throw new ConnectionManagerException(ErrorCode.ARGUMENT_VALUE_INVALID, "Unsupported direction: " + direction); } log.fine("Preparing for connection with local new ID " + connectionId + " and peer connection ID: " + peerConnectionId); ConnectionInfo newConnectionInfo = createConnection( connectionId, peerConnectionId, peerConnectionManager, dir, remoteProtocolInfo ); storeConnection(newConnectionInfo); return newConnectionInfo; } @UpnpAction synchronized public void connectionComplete(@UpnpInputArgument(name = "ConnectionID", stateVariable = "A_ARG_TYPE_ConnectionID") int connectionID) throws ActionException { ConnectionInfo info = getCurrentConnectionInfo(connectionID); log.fine("Closing connection ID " + connectionID); closeConnection(info); removeConnection(connectionID); } /** * Generate a new local connection identifier, prepare the peer, store connection details. * * @return <code>-1</code> if the {@link #peerFailure(org.teleal.cling.model.action.ActionInvocation, org.teleal.cling.model.message.UpnpResponse, String)} * method had to be called, otherwise the local identifier of the established connection. */ synchronized public int createConnectionWithPeer(final ServiceReference localServiceReference, final ControlPoint controlPoint, final Service peerService, final ProtocolInfo protInfo, final ConnectionInfo.Direction direction) { // It is important that you synchronize the whole procedure, starting with getNewConnectionID(), // then preparing the connection on the peer, then storeConnection() final int localConnectionID = getNewConnectionId(); log.fine("Creating new connection ID " + localConnectionID + " with peer: " + peerService); final boolean[] failed = new boolean[1]; new PrepareForConnection( peerService, controlPoint, protInfo, localServiceReference, localConnectionID, direction ) { @Override public void received(ActionInvocation invocation, int peerConnectionID, int rcsID, int avTransportID) { ConnectionInfo info = new ConnectionInfo( localConnectionID, rcsID, avTransportID, protInfo, peerService.getReference(), peerConnectionID, direction.getOpposite(), // If I prepared you for output, then I do input ConnectionInfo.Status.OK ); storeConnection(info); } @Override public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { AbstractPeeringConnectionManagerService.this.peerFailure( invocation, operation, defaultMsg ); failed[0] = true; } }.run(); // Synchronous execution! We "reserved" a new connection ID earlier! return failed[0] ? -1 : localConnectionID; } /** * Close the connection with the peer, remove the connection details. */ synchronized public void closeConnectionWithPeer(ControlPoint controlPoint, Service peerService, int connectionID) throws ActionException { closeConnectionWithPeer(controlPoint, peerService, getCurrentConnectionInfo(connectionID)); } /** * Close the connection with the peer, remove the connection details. */ synchronized public void closeConnectionWithPeer(final ControlPoint controlPoint, final Service peerService, final ConnectionInfo connectionInfo) throws ActionException { // It is important that you synchronize the whole procedure log.fine("Closing connection ID " + connectionInfo.getConnectionID() + " with peer: " + peerService); new ConnectionComplete( peerService, controlPoint, connectionInfo.getPeerConnectionID() ) { @Override public void success(ActionInvocation invocation) { removeConnection(connectionInfo.getConnectionID()); } @Override public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { AbstractPeeringConnectionManagerService.this.peerFailure( invocation, operation, defaultMsg ); } }.run(); // Synchronous execution! } protected abstract ConnectionInfo createConnection(int connectionID, int peerConnectionId, ServiceReference peerConnectionManager, ConnectionInfo.Direction direction, ProtocolInfo protocolInfo) throws ActionException; protected abstract void closeConnection(ConnectionInfo connectionInfo); /** * Called when connection creation or closing with a peer failed. * <p> * This is the failure result of an action invocation on the peer's connection * management service. The execution of the {@link #createConnectionWithPeer(org.teleal.cling.model.ServiceReference, org.teleal.cling.controlpoint.ControlPoint, org.teleal.cling.model.meta.Service, org.teleal.cling.support.model.ProtocolInfo , org.teleal.cling.support.model.ConnectionInfo.Direction)} * and {@link #closeConnectionWithPeer(org.teleal.cling.controlpoint.ControlPoint, org.teleal.cling.model.meta.Service, org.teleal.cling.support.model.ConnectionInfo)} * methods will block until this method completes handling any failure. * </p> * * @param invocation The underlying action invocation of the remote connection manager service. * @param operation The network message response if there was a response, or <code>null</code>. * @param defaultFailureMessage A user-friendly error message generated from the invocation exception and response. */ protected abstract void peerFailure(ActionInvocation invocation, UpnpResponse operation, String defaultFailureMessage); }