/*
* ============================================================================
* The Apache Software License, Version 1.1
* ============================================================================
*
* Copyright (C) 2002 The Apache Software Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by SuperBonBon Industries (http://www.sbbi.net/)."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "UPNPLib" and "SuperBonBon Industries" must not be
* used to endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* info@sbbi.net.
*
* 5. Products derived from this software may not be called
* "SuperBonBon Industries", nor may "SBBI" appear in their name,
* without prior written permission of SuperBonBon Industries.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
* DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* on behalf of SuperBonBon Industries. For more information on
* SuperBonBon Industries, please see <http://www.sbbi.net/>.
*/
package net.sbbi.upnp.jmx;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Class to handle HTTP UPNP requests on UPNPMBeanDevices
* @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a>
* @version 1.0
*/
public class UPNPMBeanDevicesRequestsHandler implements Runnable {
private final static Log log = LogFactory.getLog( UPNPMBeanDevicesRequestsHandler.class );
private final static int MAX_HTTP_WORKERS = 10;
private final static Map instances = new HashMap();
private Set handledDevices = new HashSet();
private Set httpWorkers = new HashSet();
private ServerSocket srv;
private boolean isRunning = false;
private InetSocketAddress bindAddress;
public final static UPNPMBeanDevicesRequestsHandler getInstance( InetSocketAddress bindAddress ) {
String key = bindAddress.toString();
synchronized( instances ) {
UPNPMBeanDevicesRequestsHandler handler = (UPNPMBeanDevicesRequestsHandler)instances.get( key );
if ( handler == null ) {
handler = new UPNPMBeanDevicesRequestsHandler( bindAddress );
instances.put( key, handler );
}
return handler;
}
}
private UPNPMBeanDevicesRequestsHandler( InetSocketAddress bindAddress ) {
this.bindAddress = bindAddress;
}
protected void addUPNPMBeanDevice( UPNPMBeanDevice rootDevice ) {
synchronized( handledDevices ) {
for ( Iterator i = handledDevices.iterator(); i.hasNext(); ) {
UPNPMBeanDevice registred = (UPNPMBeanDevice)i.next();
if ( registred.getDeviceType().equals( rootDevice.getDeviceType() ) &&
registred.getUuid().equals( rootDevice.getUuid() ) ) {
// API Use error
throw new RuntimeException( "An UPNPMBeanDevice object of type " + rootDevice.getDeviceType() +
" with uuid " + rootDevice.getUuid() +
" is already registred within this class, use a different UPNPMBeanDevice internalId" );
}
}
if ( handledDevices.size() == 0 ) {
Thread runner = new Thread( this, "UPNPMBeanDevicesRequestsHandler " + bindAddress.toString() );
runner.setDaemon( true );
runner.start();
}
handledDevices.add( rootDevice );
// adding the child devices
for ( Iterator i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext(); ) {
handledDevices.add( i.next() );
}
}
}
protected void removeUPNPMBeanDevice( UPNPMBeanDevice rootDevice ) {
synchronized( handledDevices ) {
if ( handledDevices.contains( rootDevice ) ) {
handledDevices.remove( rootDevice );
// removing the child devices
for ( Iterator i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext(); ) {
handledDevices.remove( i.next() );
}
if ( handledDevices.size() == 0 ) {
try {
isRunning = false;
srv.close();
} catch ( IOException ex ) {
// do not care
}
}
}
}
}
protected void notifyWorkerThreadEnd( HttpWorker worker ) {
synchronized( httpWorkers ) {
httpWorkers.remove( worker );
}
}
public void run() {
try {
srv = new ServerSocket( bindAddress.getPort(), 200, bindAddress.getAddress() );
} catch ( IOException ex ) {
log.error( "Error during server socket creation, thread cannot start", ex );
return;
}
isRunning = true;
while ( isRunning ) {
try {
Socket skt = srv.accept();
skt.setSoTimeout( 30000 );
HttpWorker worker = new HttpWorker(skt, log, handledDevices, this );
while ( httpWorkers.size() >= MAX_HTTP_WORKERS ) {
try {
Thread.sleep( 100 );
} catch ( InterruptedException ex ) {
// ignore
}
}
Thread workerThread = new Thread( worker, "UPNPMBeanDevicesRequestsHandler Http Worker " + httpWorkers.size() );
workerThread.start();
synchronized( httpWorkers ) {
httpWorkers.add( worker );
}
} catch ( IOException ex ) {
if ( isRunning ) {
log.error( "Error during client socket creation", ex );
}
}
}
}
private class HttpWorker implements Runnable {
private Socket client;
private Log logger;
private Set devices;
private UPNPMBeanDevicesRequestsHandler handler;
public HttpWorker( Socket client, Log log, Set handledDevices, UPNPMBeanDevicesRequestsHandler handler ) {
this.client = client;
this.logger = log;
this.devices = handledDevices;
this.handler = handler;
}
public void run() {
try {
byte[] buffer = new byte[256];
InputStream in = client.getInputStream();
StringBuffer request = new StringBuffer();
int readen = 0;
String firstReadenData = null;
while ( readen != -1 ) {
readen = in.read( buffer );
String data = new String( buffer, 0, readen );
if ( firstReadenData == null ) {
firstReadenData = data.toUpperCase();
}
request.append( data );
// either a simple get request
String rawRequest = request.toString();
if ( rawRequest.endsWith( "\r\n\r\n" ) && firstReadenData.startsWith( "GET" ) ) {
readen = -1;
} else if ( rawRequest.indexOf( "</s:Envelope>" ) != -1 ) {
// or a post request with content that should end with </s:Envelope>
readen = -1;
}
}
OutputStream out = client.getOutputStream();
String req = request.toString().trim();
if ( logger.isDebugEnabled() ) logger.debug( "Received message:\n" + req );
String toWrite = null;
if ( req.length() > 0 ) {
HttpRequest httpReq = new HttpRequest( req );
if ( httpReq.getHttpCommand() != null && httpReq.getHttpCommandArg() != null &&
httpReq.getHttpCommandArg().trim().length() > 0 ) {
String cmd = httpReq.getHttpCommand();
HttpRequestHandler handler = null;
if ( cmd.equals( "GET" ) ) {
handler = HttpGetRequest.getInstance();
// implement M-POST
} else if ( cmd.equals( "POST" ) ) {
handler = HttpPostRequest.getInstance();
} else if ( cmd.equals( "SUBSCRIBE" ) ) {
handler = HttpSubscriptionRequest.getInstance();
} else if ( cmd.equals( "UNSUBSCRIBE" ) ) {
handler = HttpSubscriptionRequest.getInstance();
}
if ( handler != null ) {
toWrite = handler.service( devices, httpReq );
}
}
}
if ( toWrite == null ) {
String content = "<html><head><title>Not found</title></head><body>The requested ressource cannot be found</body></html>";
StringBuffer rtr = new StringBuffer();
rtr.append( "HTTP/1.1 404 Not Found\r\n" );
rtr.append( "CONTENT-LENGTH: " ).append( content.length() ).append( "\r\n" );
rtr.append( "CONTENT-TYPE: text/html\r\n\r\n" );
rtr.append( content );
toWrite = rtr.toString();
}
if ( logger.isDebugEnabled() ) logger.debug( "Sending response :\n" + toWrite );
out.write( toWrite.getBytes() );
out.flush();
out.close();
in.close();
client.close();
} catch ( IOException ex ) {
logger.error( "IO Exception occured during client serving", ex );
} catch ( Throwable t ) {
logger.error( "Unexpected Exception occured during client serving", t );
} finally {
handler.notifyWorkerThreadEnd( this );
}
}
}
}