View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.support.connectionmanager;
17  
18  import org.fourthline.cling.binding.annotations.UpnpAction;
19  import org.fourthline.cling.binding.annotations.UpnpInputArgument;
20  import org.fourthline.cling.binding.annotations.UpnpOutputArgument;
21  import org.fourthline.cling.controlpoint.ControlPoint;
22  import org.fourthline.cling.model.ServiceReference;
23  import org.fourthline.cling.model.action.ActionException;
24  import org.fourthline.cling.model.action.ActionInvocation;
25  import org.fourthline.cling.model.message.UpnpResponse;
26  import org.fourthline.cling.model.meta.Service;
27  import org.fourthline.cling.model.types.ErrorCode;
28  import org.fourthline.cling.model.types.UnsignedIntegerFourBytes;
29  import org.fourthline.cling.model.types.csv.CSV;
30  import org.fourthline.cling.support.connectionmanager.callback.ConnectionComplete;
31  import org.fourthline.cling.support.connectionmanager.callback.PrepareForConnection;
32  import org.fourthline.cling.support.model.ConnectionInfo;
33  import org.fourthline.cling.support.model.ProtocolInfo;
34  import org.fourthline.cling.support.model.ProtocolInfos;
35  
36  import java.beans.PropertyChangeSupport;
37  import java.util.logging.Logger;
38  
39  /**
40   * Support for setup and teardown of an arbitrary number of connections with a manager peer.
41   *
42   * @author Christian Bauer
43   * @author Alessio Gaeta
44   */
45  public abstract class AbstractPeeringConnectionManagerService extends ConnectionManagerService {
46  
47      final private static Logger log = Logger.getLogger(AbstractPeeringConnectionManagerService.class.getName());
48  
49      protected AbstractPeeringConnectionManagerService(ConnectionInfo... activeConnections) {
50          super(activeConnections);
51      }
52  
53      protected AbstractPeeringConnectionManagerService(ProtocolInfos sourceProtocolInfo, ProtocolInfos sinkProtocolInfo,
54                                                        ConnectionInfo... activeConnections) {
55          super(sourceProtocolInfo, sinkProtocolInfo, activeConnections);
56      }
57  
58      protected AbstractPeeringConnectionManagerService(PropertyChangeSupport propertyChangeSupport,
59                                                        ProtocolInfos sourceProtocolInfo, ProtocolInfos sinkProtocolInfo,
60                                                        ConnectionInfo... activeConnections) {
61          super(propertyChangeSupport, sourceProtocolInfo, sinkProtocolInfo, activeConnections);
62      }
63  
64      synchronized protected int getNewConnectionId() {
65          int currentHighestID = -1;
66          for (Integer key : activeConnections.keySet()) {
67              if (key > currentHighestID) currentHighestID = key;
68          }
69          return ++currentHighestID;
70      }
71  
72      synchronized protected void storeConnection(ConnectionInfo info) {
73          CSV<UnsignedIntegerFourBytes> oldConnectionIDs = getCurrentConnectionIDs();
74          activeConnections.put(info.getConnectionID(), info);
75          log.fine("Connection stored, firing event: " + info.getConnectionID());
76          CSV<UnsignedIntegerFourBytes> newConnectionIDs = getCurrentConnectionIDs();
77          getPropertyChangeSupport().firePropertyChange("CurrentConnectionIDs", oldConnectionIDs, newConnectionIDs);
78      }
79  
80      synchronized protected void removeConnection(int connectionID) {
81          CSV<UnsignedIntegerFourBytes> oldConnectionIDs = getCurrentConnectionIDs();
82          activeConnections.remove(connectionID);
83          log.fine("Connection removed, firing event: " + connectionID);
84          CSV<UnsignedIntegerFourBytes> newConnectionIDs = getCurrentConnectionIDs();
85          getPropertyChangeSupport().firePropertyChange("CurrentConnectionIDs", oldConnectionIDs, newConnectionIDs);
86      }
87  
88      @UpnpAction(out = {
89              @UpnpOutputArgument(name = "ConnectionID", stateVariable = "A_ARG_TYPE_ConnectionID", getterName = "getConnectionID"),
90              @UpnpOutputArgument(name = "AVTransportID", stateVariable = "A_ARG_TYPE_AVTransportID", getterName = "getAvTransportID"),
91              @UpnpOutputArgument(name = "RcsID", stateVariable = "A_ARG_TYPE_RcsID", getterName = "getRcsID")
92      })
93      synchronized public ConnectionInfo prepareForConnection(
94              @UpnpInputArgument(name = "RemoteProtocolInfo", stateVariable = "A_ARG_TYPE_ProtocolInfo") ProtocolInfo remoteProtocolInfo,
95              @UpnpInputArgument(name = "PeerConnectionManager", stateVariable = "A_ARG_TYPE_ConnectionManager") ServiceReference peerConnectionManager,
96              @UpnpInputArgument(name = "PeerConnectionID", stateVariable = "A_ARG_TYPE_ConnectionID") int peerConnectionId,
97              @UpnpInputArgument(name = "Direction", stateVariable = "A_ARG_TYPE_Direction") String direction)
98              throws ActionException {
99  
100         int connectionId = getNewConnectionId();
101 
102         ConnectionInfo.Direction dir;
103         try {
104             dir = ConnectionInfo.Direction.valueOf(direction);
105         } catch (Exception ex) {
106             throw new ConnectionManagerException(ErrorCode.ARGUMENT_VALUE_INVALID, "Unsupported direction: " + direction);
107         }
108 
109         log.fine("Preparing for connection with local new ID " + connectionId + " and peer connection ID: " + peerConnectionId);
110 
111         ConnectionInfo newConnectionInfo = createConnection(
112                 connectionId,
113                 peerConnectionId,
114                 peerConnectionManager,
115                 dir,
116                 remoteProtocolInfo
117         );
118 
119         storeConnection(newConnectionInfo);
120 
121         return newConnectionInfo;
122     }
123 
124     @UpnpAction
125     synchronized public void connectionComplete(@UpnpInputArgument(name = "ConnectionID", stateVariable = "A_ARG_TYPE_ConnectionID") int connectionID)
126             throws ActionException {
127         ConnectionInfo info = getCurrentConnectionInfo(connectionID);
128         log.fine("Closing connection ID " + connectionID);
129         closeConnection(info);
130         removeConnection(connectionID);
131     }
132 
133     /**
134      * Generate a new local connection identifier, prepare the peer, store connection details.
135      *
136      * @return <code>-1</code> if the {@link #peerFailure(org.fourthline.cling.model.action.ActionInvocation, org.fourthline.cling.model.message.UpnpResponse, String)}
137      *         method had to be called, otherwise the local identifier of the established connection.
138      */
139     synchronized public int createConnectionWithPeer(final ServiceReference localServiceReference,
140                                                      final ControlPoint controlPoint,
141                                                      final Service peerService,
142                                                      final ProtocolInfo protInfo,
143                                                      final ConnectionInfo.Direction direction) {
144 
145         // It is important that you synchronize the whole procedure, starting with getNewConnectionID(),
146         // then preparing the connection on the peer, then storeConnection()
147 
148         final int localConnectionID = getNewConnectionId();
149 
150         log.fine("Creating new connection ID " + localConnectionID + " with peer: " + peerService);
151         final boolean[] failed = new boolean[1];
152         new PrepareForConnection(
153                 peerService,
154                 controlPoint,
155                 protInfo,
156                 localServiceReference,
157                 localConnectionID,
158                 direction
159         ) {
160             @Override
161             public void received(ActionInvocation invocation, int peerConnectionID, int rcsID, int avTransportID) {
162                 ConnectionInfo info = new ConnectionInfo(
163                         localConnectionID,
164                         rcsID,
165                         avTransportID,
166                         protInfo,
167                         peerService.getReference(),
168                         peerConnectionID,
169                         direction.getOpposite(), // If I prepared you for output, then I do input
170                         ConnectionInfo.Status.OK
171                 );
172                 storeConnection(info);
173             }
174 
175             @Override
176             public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
177                 AbstractPeeringConnectionManagerService.this.peerFailure(
178                         invocation, operation, defaultMsg
179                 );
180                 failed[0] = true;
181             }
182         }.run(); // Synchronous execution! We "reserved" a new connection ID earlier!
183 
184         return failed[0] ? -1 : localConnectionID;
185     }
186 
187     /**
188      * Close the connection with the peer, remove the connection details.
189      */
190     synchronized public void closeConnectionWithPeer(ControlPoint controlPoint,
191                                                      Service peerService,
192                                                      int connectionID) throws ActionException {
193         closeConnectionWithPeer(controlPoint, peerService, getCurrentConnectionInfo(connectionID));
194     }
195 
196     /**
197      * Close the connection with the peer, remove the connection details.
198      */
199     synchronized public void closeConnectionWithPeer(final ControlPoint controlPoint,
200                                                      final Service peerService,
201                                                      final ConnectionInfo connectionInfo) throws ActionException {
202 
203         // It is important that you synchronize the whole procedure
204         log.fine("Closing connection ID " + connectionInfo.getConnectionID() + " with peer: " + peerService);
205         new ConnectionComplete(
206                 peerService,
207                 controlPoint,
208                 connectionInfo.getPeerConnectionID()
209         ) {
210 
211             @Override
212             public void success(ActionInvocation invocation) {
213                 removeConnection(connectionInfo.getConnectionID());
214             }
215 
216             @Override
217             public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
218                 AbstractPeeringConnectionManagerService.this.peerFailure(
219                         invocation, operation, defaultMsg
220                 );
221             }
222         }.run(); // Synchronous execution!
223     }
224 
225     protected abstract ConnectionInfo createConnection(int connectionID,
226                                                        int peerConnectionId, ServiceReference peerConnectionManager,
227                                                        ConnectionInfo.Direction direction, ProtocolInfo protocolInfo) throws ActionException;
228 
229     protected abstract void closeConnection(ConnectionInfo connectionInfo);
230 
231     /**
232      * Called when connection creation or closing with a peer failed.
233      * <p>
234      * This is the failure result of an action invocation on the peer's connection
235      * management service. The execution of the {@link #createConnectionWithPeer(org.fourthline.cling.model.ServiceReference, org.fourthline.cling.controlpoint.ControlPoint, org.fourthline.cling.model.meta.Service, org.fourthline.cling.support.model.ProtocolInfo , org.fourthline.cling.support.model.ConnectionInfo.Direction)}
236      * and {@link #closeConnectionWithPeer(org.fourthline.cling.controlpoint.ControlPoint, org.fourthline.cling.model.meta.Service, org.fourthline.cling.support.model.ConnectionInfo)}
237      * methods will block until this method completes handling any failure.
238      * </p>
239      *
240      * @param invocation The underlying action invocation of the remote connection manager service.
241      * @param operation The network message response if there was a response, or <code>null</code>.
242      * @param defaultFailureMessage A user-friendly error message generated from the invocation exception and response.
243      */
244     protected abstract void peerFailure(ActionInvocation invocation, UpnpResponse operation, String defaultFailureMessage);
245 
246 }