net.sbbi.upnp.DiscoveryListener.java Source code

Java tutorial

Introduction

Here is the source code for net.sbbi.upnp.DiscoveryListener.java

Source

/*
 * ============================================================================
 *                 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;

import java.io.*;
import java.net.*;
import java.util.*;

import org.apache.commons.logging.*;

/**
 * This class can be used to listen for UPNP devices responses when a search message is sent by a control point
 * ( using the net.sbbi.upnp.Discovery.sendSearchMessage() method )
 * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a>
 * @version 1.0
 */

public class DiscoveryListener implements Runnable {

    private final static Log log = LogFactory.getLog(DiscoveryListener.class);

    private static boolean MATCH_IP = true;

    static {
        String prop = System.getProperty("net.sbbi.upnp.ddos.matchip");
        if (prop != null && prop.equals("false"))
            MATCH_IP = false;
    }

    private static final int DEFAULT_TIMEOUT = 250;

    private Map registeredHandlers = new HashMap();

    private final Object REGISTRATION_PROCESS = new Object();

    private final static DiscoveryListener singleton = new DiscoveryListener();

    private boolean inService = false;
    private boolean daemon = true;

    private java.net.MulticastSocket skt;
    private DatagramPacket input;

    private DiscoveryListener() {
    }

    public final static DiscoveryListener getInstance() {
        return singleton;
    }

    /**
     * Sets the listener as a daemon thread
     * @param daemon daemon thread
     */
    public void setDaemon(boolean daemon) {
        this.daemon = daemon;
    }

    /**
     * Registers an SSDP response message handler
     * @param resultsHandler the SSDP response message handler
     * @param searchTarget the search target
     * @throws IOException if some errors occurs during SSDP search response messages listener thread startup
     */
    public void registerResultsHandler(DiscoveryResultsHandler resultsHandler, String searchTarget)
            throws IOException {
        synchronized (REGISTRATION_PROCESS) {
            if (!inService)
                startDevicesListenerThread();
            Set handlers = (Set) registeredHandlers.get(searchTarget);
            if (handlers == null) {
                handlers = new HashSet();
                registeredHandlers.put(searchTarget, handlers);
            }
            handlers.add(resultsHandler);
        }
    }

    /**
     * Unregisters an SSDP response message handler
     * @param resultsHandler the SSDP response message handler
     * @param searchTarget the search target
     */
    public void unRegisterResultsHandler(DiscoveryResultsHandler resultsHandler, String searchTarget) {
        synchronized (REGISTRATION_PROCESS) {
            Set handlers = (Set) registeredHandlers.get(searchTarget);
            if (handlers != null) {
                handlers.remove(resultsHandler);
                if (handlers.size() == 0) {
                    registeredHandlers.remove(searchTarget);
                }
            }
            if (registeredHandlers.size() == 0) {
                stopDevicesListenerThread();
            }
        }
    }

    private void startDevicesListenerThread() throws IOException {
        synchronized (singleton) {
            if (!inService) {

                this.startMultiCastSocket();
                Thread deamon = new Thread(this, "DiscoveryListener daemon");
                deamon.setDaemon(daemon);
                deamon.start();
                while (!inService) {
                    // wait for the thread to be started let's wait a few ms
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException ex) {
                        // don t care
                    }
                }
            }
        }
    }

    private void stopDevicesListenerThread() {
        synchronized (singleton) {
            inService = false;
        }
    }

    private void startMultiCastSocket() throws IOException {
        int bindPort = Discovery.DEFAULT_SSDP_SEARCH_PORT;
        String port = System.getProperty("net.sbbi.upnp.Discovery.bindPort");
        if (port != null) {
            bindPort = Integer.parseInt(port);
        }

        skt = new java.net.MulticastSocket(null);
        skt.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), bindPort));
        skt.setTimeToLive(Discovery.DEFAULT_TTL);
        skt.setSoTimeout(DEFAULT_TIMEOUT);
        skt.joinGroup(InetAddress.getByName(Discovery.SSDP_IP));

        byte[] buf = new byte[2048];
        input = new DatagramPacket(buf, buf.length);

    }

    public void run() {
        if (!Thread.currentThread().getName().equals("DiscoveryListener daemon")) {
            throw new RuntimeException("No right to call this method");
        }
        inService = true;
        while (inService) {
            try {
                listenBroadCast();
            } catch (SocketTimeoutException ex) {
                // ignoring
            } catch (IOException ioEx) {
                log.error("IO Exception during UPNP DiscoveryListener messages listening thread", ioEx);
            } catch (Exception ex) {
                log.error("Fatal Error during UPNP DiscoveryListener messages listening thread, thread will exit",
                        ex);
                inService = false;
            }
        }

        try {
            skt.leaveGroup(InetAddress.getByName(Discovery.SSDP_IP));
            skt.close();
        } catch (Exception ex) {
            // ignoring
        }
    }

    private void listenBroadCast() throws IOException {

        skt.receive(input);
        InetAddress from = input.getAddress();
        String received = new String(input.getData(), input.getOffset(), input.getLength());
        HttpResponse msg = null;
        try {
            msg = new HttpResponse(received);
        } catch (IllegalArgumentException ex) {
            // crappy http sent
            if (log.isDebugEnabled())
                log.debug("Skipping uncompliant HTTP message " + received);
            return;
        }
        String header = msg.getHeader();
        if (header != null && header.startsWith("HTTP/1.1 200 OK") && msg.getHTTPHeaderField("st") != null) {
            // probably a search repsonse !
            String deviceDescrLoc = msg.getHTTPHeaderField("location");
            if (deviceDescrLoc == null || deviceDescrLoc.trim().length() == 0) {
                if (log.isDebugEnabled())
                    log.debug("Skipping SSDP message, missing HTTP header 'location' field");
                return;
            }
            URL loc = new URL(deviceDescrLoc);
            if (MATCH_IP) {
                InetAddress locHost = InetAddress.getByName(loc.getHost());
                if (!from.equals(locHost)) {
                    log.warn("Discovery message sender IP " + from + " does not match device description IP "
                            + locHost + " skipping device, set the net.sbbi.upnp.ddos.matchip system property"
                            + " to false to avoid this check");
                    return;
                }
            }
            if (log.isDebugEnabled())
                log.debug("Processing " + deviceDescrLoc + " device description location");
            String st = msg.getHTTPHeaderField("st");
            if (st == null || st.trim().length() == 0) {
                if (log.isDebugEnabled())
                    log.debug("Skipping SSDP message, missing HTTP header 'st' field");
                return;
            }
            String usn = msg.getHTTPHeaderField("usn");
            if (usn == null || usn.trim().length() == 0) {
                if (log.isDebugEnabled())
                    log.debug("Skipping SSDP message, missing HTTP header 'usn' field");
                return;
            }
            String maxAge = msg.getHTTPFieldElement("Cache-Control", "max-age");
            if (maxAge == null || maxAge.trim().length() == 0) {
                if (log.isDebugEnabled())
                    log.debug("Skipping SSDP message, missing HTTP header 'max-age' field");
                return;
            }
            String server = msg.getHTTPHeaderField("server");
            if (server == null || server.trim().length() == 0) {
                if (log.isDebugEnabled())
                    log.debug("Skipping SSDP message, missing HTTP header 'server' field");
                return;
            }

            String udn = usn;
            int index = udn.indexOf("::");
            if (index != -1)
                udn = udn.substring(0, index);
            synchronized (REGISTRATION_PROCESS) {
                Set handlers = (Set) registeredHandlers.get(st);
                if (handlers != null) {
                    for (Iterator i = handlers.iterator(); i.hasNext();) {
                        DiscoveryResultsHandler handler = (DiscoveryResultsHandler) i.next();
                        handler.discoveredDevice(usn, udn, st, maxAge, loc, server);
                    }
                }
            }
        } else {
            if (log.isDebugEnabled())
                log.debug("Skipping uncompliant HTTP message " + received);
        }
    }
}