de.kapsi.net.daap.bio.DaapServerBIO.java Source code

Java tutorial

Introduction

Here is the source code for de.kapsi.net.daap.bio.DaapServerBIO.java

Source

/* 
 * Digital Audio Access Protocol (DAAP)
 * Copyright (C) 2004 Roger Kapsi, info at kapsi dot de
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package de.kapsi.net.daap.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.HashSet;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.kapsi.net.daap.DaapAuthenticator;
import de.kapsi.net.daap.DaapConfig;
import de.kapsi.net.daap.DaapConnection;
import de.kapsi.net.daap.DaapFilter;
import de.kapsi.net.daap.DaapServer;
import de.kapsi.net.daap.DaapSession;
import de.kapsi.net.daap.DaapStreamSource;
import de.kapsi.net.daap.DaapThreadFactory;
import de.kapsi.net.daap.DaapUtil;
import de.kapsi.net.daap.Library;
import de.kapsi.net.daap.SimpleConfig;

/**
 * This DAAP server is written with the classical I/O and multible Threads.
 *
 * @author  Roger Kapsi
 */
public class DaapServerBIO implements DaapServer {

    private static final Log LOG = LogFactory.getLog(DaapServerBIO.class);

    private int threadNo = 0;

    private Library library;

    private DaapFilter filter;

    private HashSet sessionIds;
    private HashSet connections;
    private HashSet streams;

    private DaapConfig config;
    private DaapAuthenticator authenticator;
    private DaapStreamSource streamSource;
    private DaapThreadFactory threadFactory;

    private ServerSocket ssocket;

    private boolean running = false;

    /**
     * Creates a new DAAP server with Library and {@see SimpleConfig}
     * 
     * @param library a Library
     */
    public DaapServerBIO(Library library) {
        this(library, new SimpleConfig());
    }

    /**
     * Creates new DAAP server with Library, a {@see SimpleConfig} and 
     * the Port
     * 
     * @param library a Library
     * @param port a Port used by SimpleConfig
     */
    public DaapServerBIO(Library library, int port) {
        this(library, new SimpleConfig(port));
    }

    /**
     * Creates a new DAAP server with Library and DaapConfig
     * 
     * @param library a Library
     * @param config a DaapConfig
     */
    public DaapServerBIO(Library library, DaapConfig config) {

        this.library = library;
        this.config = config;

        threadFactory = new DaapThreadFactoryImpl();

        sessionIds = new HashSet();
        connections = new HashSet();
        streams = new HashSet();
    }

    /**
     * Returns the Library of this server
     * 
     * @return Library
     */
    public Library getLibrary() {
        return library;
    }

    /**
     * Sets the DaapConfig for this server
     * 
     * @param config DaapConfig
     */
    public void setConfig(DaapConfig config) {
        this.config = config;
    }

    /**
     * Returns the DaapConfig of this server
     * 
     * @return DaapConfig of this server
     */
    public DaapConfig getConfig() {
        return config;
    }

    /**
     * Sets the DaapAuthenticator for this server
     * 
     * @param authenticator a DaapAuthenticator
     */
    public void setAuthenticator(DaapAuthenticator authenticator) {
        this.authenticator = authenticator;
    }

    /**
     * Retrieves the DaapAuthenticator of this server
     * 
     * @return DaapAuthenticator or <code>null</code>
     */
    public DaapAuthenticator getAuthenticator() {
        return authenticator;
    }

    /**
     * Sets the DaapStreamSource for this server
     * 
     * @param streamSource a DaapStreamSource
     */
    public void setStreamSource(DaapStreamSource streamSource) {
        this.streamSource = streamSource;
    }

    /**
     * Retrieves the DaapStreamSource of this server
     * 
     * @return DaapStreamSource or <code>null</code>
     */
    public DaapStreamSource getStreamSource() {
        return streamSource;
    }

    /**
     * Sets a DaapFilter for this server
     * 
     * @param filter a DaapFilter
     */
    public void setFilter(DaapFilter filter) {
        this.filter = filter;
    }

    /**
     * Returns a DaapFilter
     * 
     * @return a DaapFilter or <code>null</code>
     */
    public DaapFilter getFilter() {
        return filter;
    }

    /**
     * Sets the DaapThreadFactory for this DAAP server
     * 
     * @param fectory a DaapThreadFactory
     */
    public void setThreadFactory(DaapThreadFactory factory) {
        if (factory == null) {
            threadFactory = new DaapThreadFactoryImpl();
        } else {
            threadFactory = factory;
        }
    }

    /**
     * Binds this server to the SocketAddress supplied by DaapConfig
     * 
     * @throws IOException
     */
    public synchronized void bind() throws IOException {
        if (running)
            return;

        SocketAddress bindAddr = config.getInetSocketAddress();
        int backlog = config.getBacklog();

        ssocket = new ServerSocket();
        ssocket.bind(bindAddr, backlog);

        if (LOG.isInfoEnabled()) {
            LOG.info("DaapServerBIO bound to " + bindAddr);
        }
    }

    /**
     * Returns <code>true</code> if DAAP Server
     * accepts incoming connections.
     */
    public synchronized boolean isRunning() {
        return running;
    }

    /**
     * Stops the DAAP Server
     */
    public synchronized void stop() {

        if (!running)
            return;

        running = false;

        try {
            if (ssocket != null)
                ssocket.close();
        } catch (IOException err) {
            LOG.error(err);
        }

        disconnectAll();
    }

    /**
     * Disconnects all DAAP and Stream connections
     */
    public synchronized void disconnectAll() {

        synchronized (connections) {
            Iterator it = connections.iterator();
            while (it.hasNext()) {
                ((DaapConnectionBIO) it.next()).disconnect();
            }
            connections.clear();
        }

        synchronized (streams) {
            Iterator it = streams.iterator();
            while (it.hasNext()) {
                ((DaapConnectionBIO) it.next()).disconnect();
            }
            streams.clear();
        }

        synchronized (sessionIds) {
            sessionIds.clear();
        }
    }

    /**
     * Adds connection to the internal connection pool and returns true
     * on success. False is retuned in the following cases: Max connections
     * reached or server is down.
     */
    synchronized boolean addConnection(DaapConnectionBIO connection) {

        if (!isRunning()) {

            if (LOG.isInfoEnabled()) {
                LOG.info("Server is down.");
            }

            return false;
        }

        if (connection.isAudioStream()) {

            synchronized (streams) {

                if (streams.size() < config.getMaxConnections()) {
                    streams.add(connection);

                } else {

                    if (LOG.isInfoEnabled()) {
                        LOG.info("Connection limit reached");
                    }

                    return false;
                }
            }

        } else {

            synchronized (connections) {

                if (connections.size() < config.getMaxConnections()) {
                    connections.add(connection);

                } else {

                    if (LOG.isInfoEnabled()) {
                        LOG.info("Connection limit reached");
                    }

                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Accepts an incoming connection.
     */
    private boolean accept(Socket socket) throws IOException {

        if (filter != null && !filter.accept(socket.getInetAddress())) {

            if (LOG.isInfoEnabled()) {
                LOG.info("DaapFilter refused connection from " + socket);
            }

            return false;
        }

        DaapConnectionBIO connection = new DaapConnectionBIO(this, socket);

        Thread connThread = threadFactory.createDaapThread(connection, "DaapConnectionThread-" + (++threadNo));
        connThread.start();

        return true;
    }

    /**
     * Call this to notify the server that Library has changed
     */
    public void update() {
        synchronized (connections) {
            Iterator it = connections.iterator();
            while (it.hasNext()) {

                DaapConnectionBIO conn = (DaapConnectionBIO) it.next();

                try {
                    conn.update();
                } catch (IOException err) {
                    LOG.error(err);
                }
            }
        }
    }

    /**
     * Removes connection from the internal connection pool
     */
    void removeConnection(DaapConnectionBIO connection) {

        if (connection.isAudioStream()) {

            synchronized (streams) {
                streams.remove(connection);
            }

        } else {

            synchronized (connections) {
                connections.remove(connection);
            }

            DaapSession session = connection.getSession(false);
            if (session != null) {
                session.invalidate();

                synchronized (sessionIds) {
                    sessionIds.remove(session.getSessionId());
                }
            }
        }
    }

    /**
     * Returns <code>true</code> if sessionId is known and valid
     */
    boolean isSessionIdValid(int sessionId) {
        return isSessionIdValid(new Integer(sessionId));
    }

    /**
     * Returns <code>true</code> if sessionId is known and valid
     * 
     * <p>DO NOT CALL THIS METHOD! THIS METHOD IS ONLY PUBLIC 
     * DUE TO SOME DESIGN ISSUES!</p>
     */
    public boolean isSessionIdValid(Integer sessionId) {
        synchronized (sessionIds) {
            return sessionIds.contains(sessionId);
        }
    }

    /**
     * Retrieves a DaapConnection for a session ID or <code>null</code>.
     * 
     * <p>DO NOT CALL THIS METHOD! THIS METHOD IS ONLY PUBLIC 
     * DUE TO SOME DESIGN ISSUES!</p>
     * 
     * @param sessionId a session ID
     * @return a DaapConnection or <code>null</code>
     */
    public DaapConnection getConnection(Integer sessionId) {
        synchronized (connections) {
            Iterator it = connections.iterator();
            while (it.hasNext()) {
                DaapConnection connection = (DaapConnection) it.next();
                DaapSession session = connection.getSession(false);
                if (session != null) {
                    Integer sid = session.getSessionId();
                    if (sid.equals(sessionId)) {
                        return connection;
                    }
                }
            }
            return null;
        }
    }

    /**
     * Creates and unique sessionId and returns it
     * 
     * <p>DO NOT CALL THIS METHOD! THIS METHOD IS ONLY PUBLIC 
     * DUE TO SOME DESIGN ISSUES!</p>
     */
    public Integer createSessionId() {
        synchronized (sessionIds) {
            Integer sid = DaapUtil.createSessionId(sessionIds);
            sessionIds.add(sid);
            return sid;
        }
    }

    /**
     * Returns the number of connections
     */
    public int getNumberOfConnections() {
        if (connections == null)
            return 0;

        synchronized (connections) {
            return connections.size();
        }
    }

    /**
     * Returns the number of streams
     */
    public int getNumberOfStreams() {
        if (streams == null)
            return 0;

        synchronized (streams) {
            return streams.size();
        }
    }

    /**
     * The run loop
     */
    public void run() {

        threadNo = 0;
        running = true;

        try {

            while (running) {
                Socket socket = ssocket.accept();

                try {

                    if (running && !accept(socket)) {

                        socket.close();
                    }

                } catch (IOException err) {
                    LOG.error(err);
                    try {
                        socket.close();
                    } catch (IOException ignored) {
                    }
                }

                Thread.sleep(100);
            }

        } catch (InterruptedException err) {
            LOG.error(err);
            //   throw new RuntimeException(err);

        } catch (SocketException err) {
            if (running) {
                LOG.error(err);
            }
            //  throw new RuntimeException(err);

        } catch (IOException err) {
            LOG.error(err);
            //  throw new RuntimeException(err);

        } finally {
            stop();
        }
    }

    /**
     * The default DaapThreadFactory
     */
    private static class DaapThreadFactoryImpl implements DaapThreadFactory {

        private DaapThreadFactoryImpl() {
        }

        public Thread createDaapThread(Runnable runnable, String name) {
            Thread thread = new Thread(runnable, name);
            thread.setDaemon(true);
            return thread;
        }
    }
}