FileDocCategorySizeDatePackage
UPnPImpl.javaAPI DocAzureus 3.0.3.423145Tue Sep 25 13:45:16 BST 2007com.aelitis.net.upnp.impl

UPnPImpl

public class UPnPImpl extends org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderAdapter implements SSDPIGDListener, UPnP
author
parg

Fields Summary
public static final String
NL
private static UPnPImpl
singleton
private static org.gudy.azureus2.core3.util.AEMonitor
class_mon
private UPnPAdapter
adapter
private SSDPIGD
ssdp
private Map
root_locations
private List
log_listeners
private List
log_history
private List
log_alert_history
private List
rd_listeners
private org.gudy.azureus2.core3.util.AEMonitor
rd_listeners_mon
private int
http_calls_ok
private int
direct_calls_ok
private int
trace_index
private org.gudy.azureus2.core3.util.ThreadPool
device_dispatcher
private Set
device_dispatcher_pending
protected org.gudy.azureus2.core3.util.AEMonitor
this_mon
Constructors Summary
protected UPnPImpl(UPnPAdapter _adapter, String[] _selected_interfaces)


	
	
				
				 
	
		 
	
		adapter	= _adapter;
		
		ssdp = SSDPIGDFactory.create( this, _selected_interfaces );
		
		ssdp.addListener(this);
		
		ssdp.start();
	
Methods Summary
public voidaddLogListener(UPnPLogListener l)

		List	old_logs;
		List	old_alerts;
		
		try{
			this_mon.enter();

			old_logs 	= new ArrayList(log_history);
			old_alerts 	= new ArrayList(log_alert_history);

			log_listeners.add( l );
		}finally{
			
			this_mon.exit();
		}
		
		for (int i=0;i<old_logs.size();i++){
			
			l.log((String)old_logs.get(i));
		}
		
		for (int i=0;i<old_alerts.size();i++){
			
			Object[]	entry = (Object[])old_alerts.get(i);
			
			l.logAlert((String)entry[0], ((Boolean)entry[1]).booleanValue(), ((Integer)entry[2]).intValue());
		}
	
public voidaddRootDeviceListener(UPnPListener l)

		List	old_locations;
		
		try{
			this_mon.enter();

			old_locations = new ArrayList(root_locations.values());

			rd_listeners.add( l );
			
		}finally{
			
			this_mon.exit();
		}
		
		for (int i=0;i<old_locations.size();i++){
			
			UPnPRootDevice	device = (UPnPRootDevice)old_locations.get(i);
			
			try{
				
				if ( l.deviceDiscovered( device.getUSN(), device.getLocation())){
					
					l.rootDeviceFound(device);
				}
				
			}catch( Throwable e ){
				
				Debug.printStackTrace(e);
			}
		}
	
public org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentdownloadXML(UPnPDeviceImpl device, java.net.URL url)

		try{
				// some devices have borked relative urls, work around
			
			if ( device != null ){
				
				device.restoreRelativeBaseURL();
			}
			
			return( downloadXMLSupport( device, url ));
			
		}catch( UPnPException e ){
			
			if ( device != null ){
				
				device.clearRelativeBaseURL();
				
				return( downloadXMLSupport( device, url ));
			}
			
			throw( e );
		}
	
public org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentdownloadXML(java.net.URL url)

		return( downloadXML( null, url ));
	
protected org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentdownloadXMLSupport(UPnPDeviceImpl device, java.net.URL url)

		try{
			if ( forceDirect()){
			
				Socket	socket = new Socket(url.getHost(), url.getPort());
				
				try{
					PrintWriter	pw = new PrintWriter(new OutputStreamWriter( socket.getOutputStream(), "UTF8" ));
				
					String	url_target = url.toString();
					
					int	p1 	= url_target.indexOf( "://" ) + 3;
					p1		= url_target.indexOf( "/", p1 );
					
					url_target = url_target.substring( p1 );
					
					pw.print( "GET " + url_target + " HTTP/1.1" + NL );
					pw.print( "User-Agent: Azureus (UPnP/1.0)" + NL );
					pw.print( "Host: " + url.getHost() + NL );
					pw.print( "Connection: Close" + NL );
					pw.print( "Pragma: no-cache" + NL + NL );
						
					pw.flush();
					
					InputStream	is = HTTPUtils.decodeChunkedEncoding( socket.getInputStream());
					
					return( parseXML( is ));
					
				}finally{
					
					try{
						socket.close();
						
					}catch( Throwable e ){
						
						Debug.printStackTrace(e);
					}
				}
			}else{
				
				ResourceDownloaderFactory rdf = adapter.getResourceDownloaderFactory();
				
				ResourceDownloader rd = rdf.getRetryDownloader( rdf.create( url ), 3 );
				
				rd.addListener( this );
				
				InputStream	data = rd.download();
					
				try{
					return( parseXML( data ));
					
				}finally{
					
					data.close();
				}
			}
		}catch( Throwable e ){
				
			adapter.log( Debug.getNestedExceptionMessageAndStack(e));

			if (e instanceof UPnPException ){
				
				throw((UPnPException)e);
			}
			
			throw( new UPnPException( "Root device location '" + url + "' - data read failed", e ));
		}	
	
public voidfailed(org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader downloader, org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderException e)

		log( e );
	
protected booleanforceDirect()

		String	http_proxy 	= System.getProperty( "http.proxyHost" );
		String	socks_proxy = System.getProperty( "socksProxyHost" );

			// extremely unlikely we want to proxy upnp requests
		
		boolean force_direct = 	( http_proxy != null && http_proxy.trim().length() > 0 ) ||
								( socks_proxy != null && socks_proxy.trim().length() > 0 );

		return( force_direct );
	
public UPnPAdaptergetAdapter()

		return( adapter );
	
public static UPnPgetSingleton(UPnPAdapter adapter, java.lang.String[] selected_interfaces)

	
	  
	
				
				 
	
		 
	
		try{
			class_mon.enter();
		
			if ( singleton == null ){
				
				singleton = new UPnPImpl( adapter, selected_interfaces );
			}
			
			return( singleton );
			
		}finally{
			
			class_mon.exit();
		}
	
protected java.io.FilegetTraceFile()

		try{
			this_mon.enter();
		
			trace_index++;
			
			if ( trace_index == 6 ){
				
				trace_index = 1;
			}
			
			return( new File( adapter.getTraceDir(), "upnp_trace" + trace_index + ".log" ));
		}finally{
			
			this_mon.exit();
		}
	
public voidinterfaceChanged(java.net.NetworkInterface network_interface)

		reset();
	
public voidlog(java.lang.Throwable e)

		log( e.toString());
	
public voidlog(java.lang.String str)

		List	old_listeners;
		
		try{
			this_mon.enter();

			old_listeners = new ArrayList(log_listeners);

			log_history.add( str );
			
			if ( log_history.size() > 32 ){
				
				log_history.remove(0);
			}
		}finally{
			
			this_mon.exit();
		}
		
		for (int i=0;i<old_listeners.size();i++){
	
			((UPnPLogListener)old_listeners.get(i)).log( str );
		}
	
public voidlogAlert(java.lang.String str, boolean error, int type)

		List	old_listeners;
		
		try{
			this_mon.enter();

			old_listeners = new ArrayList(log_listeners);

			log_alert_history.add(new Object[]{ str, new Boolean( error ), new Integer( type )});
			
			if ( log_alert_history.size() > 32 ){
				
				log_alert_history.remove(0);
			}
		}finally{
			
			this_mon.exit();
		}
		
		for (int i=0;i<old_listeners.size();i++){
	
			((UPnPLogListener)old_listeners.get(i)).logAlert( str, error, type );
		}
	
public org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentparseXML(java.io.InputStream _is)

			// ASSUME UTF-8
		
		ByteArrayOutputStream		baos = null;
		
		try{
			baos = new ByteArrayOutputStream(1024);
			
			byte[]	buffer = new byte[8192];
			
			while(true){
				
				int	len = _is.read( buffer );
				
				if ( len <= 0 ){
					
					break;
				}
				
				baos.write( buffer, 0, len );
			}
		}finally{
			
			baos.close();
		}
		
		byte[]	bytes_in = baos.toByteArray();
		
		InputStream	is = new ByteArrayInputStream( bytes_in );
		
			// Gudy's router was returning trailing nulls which then stuffed up the
			// XML parser. Hence this code to try and strip them
		
		try{
			StringBuffer	data = new StringBuffer(1024);
			
			LineNumberReader	lnr = new LineNumberReader( new InputStreamReader( is, "UTF-8" ));
			
			Set	ignore_map = null;
			
			while( true ){
				
				String	line = lnr.readLine();
				
				if ( line == null ){
					
					break;
				}
				
					// remove any obviously invalid characters - I've seen some routers generate stuff like
					// 0x18 which stuffs the xml parser with "invalid unicode character"
				
				for (int i=0;i<line.length();i++){
					
					char	c = line.charAt(i);
				
					if ( c < 0x20 && c != '\r" && c != '\t" ){
						
						data.append( ' " );
						
						if ( ignore_map == null ){
							
							ignore_map = new HashSet();
						}
						
						Character	cha = new Character(c);
						
						if ( !ignore_map.contains( cha )){
						
							ignore_map.add( cha );
							
							adapter.trace( "    ignoring character(s) " + (int)c + " in xml response" );
						}
					}else{
						
						data.append( c );
					}
				}
				
				data.append( "\n" );				
			}
				
			String	data_str = data.toString();
			
			adapter.trace( "UPnP:Response:" + data_str );
			
			return( adapter.parseXML( data_str ));
			
		}catch( Throwable e ){
			
			try{
				FileOutputStream	trace = new FileOutputStream( getTraceFile());
				
				trace.write( bytes_in );
				
				trace.close();
				
			}catch( Throwable f ){
				
				adapter.log(f);
			}
			
			if ( e instanceof SimpleXMLParserDocumentException ){
				
				throw((SimpleXMLParserDocumentException)e);
			}
			
			throw( new SimpleXMLParserDocumentException(e ));
		}
	
public org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentperformSOAPRequest(UPnPService service, java.lang.String soap_action, java.lang.String request)

		SimpleXMLParserDocument	res;
				
		if ( service.getDirectInvocations() || forceDirect()){
			
			res = performSOAPRequest( service, soap_action, request, false );

		}else{
			
			try{
				res =  performSOAPRequest( service, soap_action, request, true );
					
				http_calls_ok++;
				
			}catch( IOException e ){
				
				res = performSOAPRequest( service, soap_action, request, false );
				
				direct_calls_ok++;
				
				if ( direct_calls_ok == 1 ){
					
					log( "Invocation via http connection failed (" + e.getMessage() + ") but socket connection succeeded" );
				}
			}
		}
		
		return( res );
	
public org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentperformSOAPRequest(UPnPService service, java.lang.String soap_action, java.lang.String request, boolean use_http_connection)

		URL	control = service.getControlURL();
		
		adapter.trace( "UPnP:Request: -> " + control + "," + request );

		if ( use_http_connection ){
			
			HttpURLConnection	con = (HttpURLConnection)control.openConnection();
			
			con.setRequestProperty( "SOAPAction", "\""+ soap_action + "\"");
			
			con.setRequestProperty( "Content-Type", "text/xml; charset=\"utf-8\"" );
			
			con.setRequestProperty( "User-Agent", "Azureus (UPnP/1.0)" );
			
			con.setRequestMethod( "POST" );
			
			con.setDoInput( true );
			con.setDoOutput( true );
			
			OutputStream	os = con.getOutputStream();
			
			PrintWriter	pw = new PrintWriter( new OutputStreamWriter(os, "UTF-8" ));
						
			pw.println( request );
			
			pw.flush();
	
			con.connect();
			
			if ( con.getResponseCode() == 405 || con.getResponseCode() == 500 ){
				
					// gotta retry with M-POST method
								
				con = (HttpURLConnection)control.openConnection();
				
				con.setRequestProperty( "Content-Type", "text/xml; charset=\"utf-8\"" );
				
				con.setRequestMethod( "M-POST" );
				
				con.setRequestProperty( "MAN", "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=01" );
	
				con.setRequestProperty( "01-SOAPACTION", "\""+ soap_action + "\"");
				
				con.setDoInput( true );
				con.setDoOutput( true );
				
				os = con.getOutputStream();
				
				pw = new PrintWriter( new OutputStreamWriter(os, "UTF-8" ));
							
				pw.println( request );
				
				pw.flush();
	
				con.connect();
			
				return( parseXML(con.getInputStream()));	
				
			}else{
				
				return( parseXML(con.getInputStream()));
			}
		}else{
	
			Socket	socket = new Socket(control.getHost(), control.getPort());
			
			try{
				PrintWriter	pw = new PrintWriter(new OutputStreamWriter( socket.getOutputStream(), "UTF8" ));
			
				String	url_target = control.toString();
				
				int	p1 	= url_target.indexOf( "://" ) + 3;
				p1		= url_target.indexOf( "/", p1 );
				
				url_target = url_target.substring( p1 );
				
				pw.print( "POST " + url_target + " HTTP/1.1" + NL );
				pw.print( "Content-Type: text/xml; charset=\"utf-8\"" + NL );
				pw.print( "SOAPAction: \"" + soap_action + "\"" + NL );
				pw.print( "User-Agent: Azureus (UPnP/1.0)" + NL );
				pw.print( "Host: " + control.getHost() + NL );
				pw.print( "Content-Length: " + request.getBytes( "UTF8" ).length + NL );
				pw.print( "Connection: Keep-Alive" + NL );
				pw.print( "Pragma: no-cache" + NL + NL );
	
				pw.print( request );
				
				pw.flush();
				
				InputStream	is = HTTPUtils.decodeChunkedEncoding( socket.getInputStream());
				
				return( parseXML( is ));
				
			}finally{
				
				try{
					socket.close();
					
				}catch( Throwable e ){
					
					Debug.printStackTrace(e);
				}
			}
		}
	
public voidremoveLogListener(UPnPLogListener l)

		log_listeners.remove( l );
	
public voidremoveRootDeviceListener(UPnPListener l)

		try{
			this_mon.enter();

			rd_listeners.remove( l );
			
		}finally{
			
			this_mon.exit();
		}
	
public voidreportActivity(org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader downloader, java.lang.String activity)

		log( activity );
	
public voidreset()

		log( "UPnP: reset" );

		List	roots;
		
		try{
			rd_listeners_mon.enter();

			roots = new ArrayList(root_locations.values());
			
			root_locations.clear();
			
		}finally{
			
			rd_listeners_mon.exit();
		}
		
		for (int i=0;i<roots.size();i++){
			
			((UPnPRootDeviceImpl)roots.get(i)).destroy( true );
		}
		
		ssdp.searchNow();
	
public voidrootAlive(java.lang.String usn, java.net.URL location)

		UPnPRootDeviceImpl root_device = (UPnPRootDeviceImpl)root_locations.get( usn );
			
		if ( root_device == null ){
			
			ssdp.searchNow();
		}
	
public voidrootDiscovered(java.net.NetworkInterface network_interface, java.net.InetAddress local_address, java.lang.String usn, java.net.URL location)

		
			// we need to take this operation off the main thread as it can take some time. This is a single
			// concurrency queued thread pool so things get done serially in the right order
		
		try{
			rd_listeners_mon.enter();

			if ( device_dispatcher_pending.contains( usn )){
			
				// System.out.println( "UPnP: skipping discovery of " + usn + " as already pending (queue=" + device_dispatcher_pending.size() + ")" );
				
				return;
			}
			
			if ( device_dispatcher_pending.size() > 512 ){
				
				Debug.out( "Device dispatcher queue is full - dropping discovery of " + usn + "/" + location );
			}
			
			device_dispatcher_pending.add( usn );
	
		}finally{
			
			rd_listeners_mon.exit();
		}
		
		device_dispatcher.run(
			new AERunnable()
			{
				public void
				runSupport()
				{
					final UPnPRootDeviceImpl old_root_device;
					
					try{
						rd_listeners_mon.enter();

						old_root_device = (UPnPRootDeviceImpl)root_locations.get( usn );
						
						device_dispatcher_pending.remove( usn );
						
					}finally{
						
						rd_listeners_mon.exit();
					}
					
					if ( old_root_device != null ){
				
							// we remember one route to the device - if the network interfaces change
							// we do a full reset so we don't need to deal with that here
						
						if ( !old_root_device.getNetworkInterface().getName().equals( network_interface.getName())){
							
							return;
						}
						
							// check that the device's location is the same
												
						if ( old_root_device.getLocation().equals( location )){		

							return;
						}
					}
					
					if ( old_root_device != null ){
						
							// something changed, resetablish everything
						
						try{
								// not the best "atomic" code here but it'll do as the code that adds roots (this)
								// is single threaded via the dispatcher
							
							rd_listeners_mon.enter();

							root_locations.remove( usn );
						
						}finally{
							
							rd_listeners_mon.exit();
						}
						
						old_root_device.destroy( true );
					}
		
					List	listeners;
					
					try{
						rd_listeners_mon.enter();
						
						listeners = new ArrayList( rd_listeners );
						
					}finally{
						
						rd_listeners_mon.exit();
					}
					
					for (int i=0;i<listeners.size();i++){
						
						try{
							if ( !((UPnPListener)listeners.get(i)).deviceDiscovered( usn, location )){
								
								return;
							}
							
						}catch( Throwable e ){
							
							Debug.printStackTrace(e);
						}
					}				

					log( "UPnP: root discovered: usn=" + usn + ", location=" + location + ", ni=" + network_interface.getName() + ",local=" + local_address.toString() );
					
					try{
						UPnPRootDeviceImpl new_root_device = new UPnPRootDeviceImpl( UPnPImpl.this, network_interface, local_address, usn, location );
											
						try{
							rd_listeners_mon.enter();
							
							root_locations.put( usn, new_root_device );
								
							listeners = new ArrayList( rd_listeners );

						}finally{
							
							rd_listeners_mon.exit();
						}
			
						for (int i=0;i<listeners.size();i++){
							
							try{
								((UPnPListener)listeners.get(i)).rootDeviceFound( new_root_device );
								
							}catch( Throwable e ){
								
								Debug.printStackTrace(e);
							}
						}
					
					}catch( UPnPException e ){
						
						String	message = e.getMessage();
						
						String msg = message==null?Debug.getNestedExceptionMessageAndStack( e ):message;
												
						adapter.log( msg );
					}
				}
			});
	
public voidrootLost(java.net.InetAddress local_address, java.lang.String usn)

			// we need to take this operation off the main thread as it can take some time
		
		device_dispatcher.run(
			new AERunnable()
			{
				public void
				runSupport()
				{
					UPnPRootDeviceImpl	root_device	= null;
									
					try{
						rd_listeners_mon.enter();
			
						root_device = (UPnPRootDeviceImpl)root_locations.remove( usn );
				
					}finally{
						
						rd_listeners_mon.exit();
					}
					
					if ( root_device == null ){
						
						return;
					}
									
					log( "UPnP: root lost: usn=" + usn + ", location=" + root_device.getLocation() + ", ni=" + root_device.getNetworkInterface().getName() + ",local=" + root_device.getLocalAddress().toString());
				
					root_device.destroy( false );
				}
			});