de.kapsi.net.daap.DaapRequest.java Source code

Java tutorial

Introduction

Here is the source code for de.kapsi.net.daap.DaapRequest.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;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A DAAP request. The data of this class is submitted by the client 
 * (e.g. iTunes) and the data is used to create a appropriate 
 * DaapResponse.
 * 
 * @author  Roger Kapsi
 */
public class DaapRequest {

    /** "/server-info" */
    public static final int SERVER_INFO = 1;

    /** "/content-codes" */
    public static final int CONTENT_CODES = 2;

    /** "/login" */
    public static final int LOGIN = 3;

    /** "/logout" */
    public static final int LOGOUT = 4;

    /** "/update" */
    public static final int UPDATE = 5;

    /** "/resolve" */
    public static final int RESOLVE = 6;

    /** "/databases" */
    public static final int DATABASES = 7;

    /** "/databases/databaseId/items" */
    public static final int DATABASE_SONGS = 8;

    /** "/databases/databaseId/containers" */
    public static final int DATABASE_PLAYLISTS = 9;

    /** "/databases/databaseId/containers/containerId/items" */
    public static final int PLAYLIST_SONGS = 10;

    /** "/databases/databaseId/items/itemId.format" */
    public static final int SONG = 11;

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

    private String method;
    private URI uri;
    private String protocol;

    private Map queryMap;

    private int sessionId = DaapUtil.NULL;
    private int revisionNumber = DaapUtil.NULL;
    private int delta = DaapUtil.NULL;

    private ArrayList meta;
    private String metaString;

    private int requestType = DaapUtil.NULL;
    private int databaseId = DaapUtil.NULL;
    private int containerId = DaapUtil.NULL;
    private int itemId = DaapUtil.NULL;

    private ArrayList headers;
    private boolean isServerSideRequest;
    private boolean isUpdateType;

    private DaapConnection connection;

    /**
     * Create a new DaapRequest
     */
    private DaapRequest(DaapConnection connection) {
        this.connection = connection;
        headers = new ArrayList();
    }

    /**
     * Creates a server side fake update DaapRequest to issue an update
     *
     * @param sessionId
     * @param revisionNumber
     * @param delta
     */
    public DaapRequest(DaapConnection connection, int sessionId, int revisionNumber, int delta) {
        this(connection);

        this.sessionId = sessionId;
        this.revisionNumber = revisionNumber;
        this.delta = delta;

        this.requestType = UPDATE;
        this.isServerSideRequest = true;
        this.isUpdateType = false;
    }

    /**
     * Creates a DaapRequest from the the requestLine
     *
     * @param requestLine
     * @throw URIException
     */
    public DaapRequest(DaapConnection connection, String requestLine) throws URIException {
        this(connection);

        String method = null;
        URI uri = null;
        String protocol = null;

        try {
            StringTokenizer st = new StringTokenizer(requestLine, " ");
            method = st.nextToken();

            try {
                uri = new URI(st.nextToken().toCharArray());
            } catch (URIException err) {
                if (LOG.isErrorEnabled()) {
                    LOG.error(err);
                }
            }

            protocol = st.nextToken();
        } catch (NoSuchElementException err) {
            if (LOG.isErrorEnabled()) {
                LOG.error(err);
            }
        }

        this.isServerSideRequest = false;
        this.isUpdateType = false;

        setMethod(method);
        setURI(uri);
        setProtocol(protocol);
    }

    /**
     * Creates a new DaapRequest
     *
     * @param method
     * @param uri
     * @param protocol
     * @throw URIException
     */
    public DaapRequest(DaapConnection connection, String method, URI uri, String protocol) throws URIException {
        this(connection);

        this.isServerSideRequest = false;
        this.isUpdateType = false;

        setMethod(method);
        setURI(uri);
        setProtocol(protocol);
    }

    /**
     * Sets the request method (GET)
     *
     * @param method
     */
    private void setMethod(String method) {
        this.method = method;
    }

    /**
     * Sets the protocol of the request (HTTP/1.1)
     *
     * @param protocol
     */
    private void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    /**
     * Sets and parses the URI. Note: if URIException is
     * thrown then is this Request in an inconsistent state!
     *
     * @param uri
     * @throws URIException
     */
    private void setURI(URI uri) throws URIException {

        this.uri = uri;

        if (uri != null) {

            String path = uri.getPath();

            this.queryMap = DaapUtil.parseQuery(uri.getQuery());

            if (path.equals("/server-info")) {
                requestType = SERVER_INFO;
            } else if (path.equals("/content-codes")) {
                requestType = CONTENT_CODES;
            } else if (path.equals("/login")) {
                requestType = LOGIN;
            } else if (path.equals("/logout")) {
                requestType = LOGOUT;
            } else if (path.equals("/update")) {
                requestType = UPDATE;
            } else if (path.equals("/resolve")) {
                requestType = RESOLVE;
            }

            if (queryMap.containsKey("session-id")) {
                sessionId = Integer.parseInt((String) queryMap.get("session-id"));
            }

            if (sessionId != DaapUtil.NULL) {

                if (queryMap.containsKey("revision-number")) {
                    revisionNumber = Integer.parseInt((String) queryMap.get("revision-number"));
                }

                if (queryMap.containsKey("delta")) {
                    delta = Integer.parseInt((String) queryMap.get("delta"));
                }

                if (queryMap.containsKey("meta")) {
                    metaString = (String) queryMap.get("meta");
                }

                isUpdateType = (delta != DaapUtil.NULL) && (delta < revisionNumber);

                // "/databases/id/items"                3 tokens
                // "/databases/id/containers"           3 tokens
                // "/databases/id/items/id.format"      4 tokens
                // "/databases/id/containers/id/items"  5 tokens
                if (path.equals("/databases")) {
                    requestType = DATABASES;

                } else if (path.startsWith("/databases")) {

                    StringTokenizer tok = new StringTokenizer(path, "/");
                    int count = tok.countTokens();

                    if (count >= 3) {
                        String token = tok.nextToken();

                        if (token.equals("databases") == false) {
                            throw new URIException("Unknown token in path: " + path + " [" + token + "]@1");
                        }

                        databaseId = Integer.parseInt((String) tok.nextToken());
                        token = tok.nextToken();

                        if (token.equals("items")) {
                            requestType = DATABASE_SONGS;
                        } else if (token.equals("containers")) {
                            requestType = DATABASE_PLAYLISTS;
                        } else {
                            throw new URIException("Unknown token in path: " + path + " [" + token + "]@2");
                        }

                        if (count == 3) {
                            // do nothing...

                        } else if (count == 4) {

                            token = (String) tok.nextToken();

                            StringTokenizer fileTokenizer = new StringTokenizer(token, ".");

                            if (fileTokenizer.countTokens() == 2) {
                                itemId = Integer.parseInt(fileTokenizer.nextToken());
                                requestType = SONG;

                            } else {
                                throw new URIException("Unknown token in path: " + path + " [" + token + "]@3");
                            }

                        } else if (count == 5) {
                            containerId = Integer.parseInt((String) tok.nextToken());
                            token = (String) tok.nextToken();

                            if (token.equals("items")) {
                                requestType = PLAYLIST_SONGS;

                            } else {
                                throw new URIException("Unknown token in path: " + path + " [" + token + "@4");
                            }

                        } else {
                            throw new URIException("Unknown token in path: " + path + " [" + token + "]@5");
                        }
                    } else {
                        throw new URIException("Unknown token in path: " + path);
                    }
                }
            }

        } else {

            queryMap = null;
            metaString = null;
            isUpdateType = false;

            requestType = DaapUtil.NULL;
            databaseId = DaapUtil.NULL;
            containerId = DaapUtil.NULL;
            itemId = DaapUtil.NULL;

            sessionId = DaapUtil.NULL;
            revisionNumber = DaapUtil.NULL;
            delta = DaapUtil.NULL;
        }
    }

    /**
     * Adds an array of Headers to this requests
     * list of Headers
     *
     * @return
     */
    public void addHeaders(Header[] headers) {
        for (int i = 0; i < headers.length; i++)
            this.headers.add(headers[i]);
    }

    /**
     * Adds a list of headers to this requests
     * list
     *
     * @return
     */
    public void addHeaders(List headers) {
        if (this.headers != headers)
            this.headers.addAll(headers);
    }

    /**
     * Adds <code>header</code> to the list
     *
     * @return
     */
    public void addHeader(Header header) {
        this.headers.add(header);
    }

    /**
     * Returns the entire list of Headers
     *
     * @return
     */
    public List getHeaders() {
        return headers;
    }

    /**
     * Returns a Header for the key or <code>null</code> if
     * no such Header is in the list
     *
     * @return
     */
    public Header getHeader(String key) {

        if (headers == null)
            return null;

        Iterator it = headers.iterator();
        while (it.hasNext()) {
            Header header = (Header) it.next();
            if (header.getName().equals(key)) {
                return header;
            }
        }

        return null;
    }

    /**
     * Returns the associated DaapConnection
     */
    public DaapConnection getConnection() {
        return connection;
    }

    /**
     * Returns <code>true</code> if this is an unknown
     * request
     *
     * @return
     */
    public boolean isUnknownRequest() {
        return (requestType == DaapUtil.NULL);
    }

    /**
     * Returns <code>true</code> if this is a server info
     * request
     *
     * <p><i>GET /server-info HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isServerInfoRequest() {
        return (requestType == SERVER_INFO);
    }

    /**
     * Returns <code>true</code> if this is a content
     * codes request
     *
     * <p><i>GET /content-codes HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isContentCodesRequest() {
        return (requestType == CONTENT_CODES);
    }

    /**
     * Returns <code>true</code> if this is a login
     * request
     *
     * <p><i>GET /login HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isLoginRequest() {
        return (requestType == LOGIN);
    }

    /**
     * Returns <code>true</code> if this is a logout
     * request
     *
     * <p><i>GET /logout HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isLogoutRequest() {
        return (requestType == LOGOUT);
    }

    /**
     * Returns <code>true</code> if this is an update
     * request
     *
     * <p><i>GET /update HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isUpdateRequest() {
        return (requestType == UPDATE);
    }

    /**
     * Returns <code>true</code> if this is a resolve
     * request <i>(not supported)</i>
     *
     * <p><i>GET /resolve HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isResolveRequest() {
        return (requestType == RESOLVE);
    }

    /**
     * Returns <code>true</code> if this is a databases
     * request
     *
     * <p><i>GET /databases HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isDatabasesRequest() {
        return (requestType == DATABASES);
    }

    /**
     * Returns <code>true</code> if this is a database
     * songs request
     *
     * <p><i>GET /databases/databaseId/items HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isDatabaseSongsRequest() {
        return (requestType == DATABASE_SONGS);
    }

    /**
     * Returns <code>true</code> if this is a database
     * playlists request
     *
     * <p><i>GET /databases/databaseId/containers HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isDatabasePlaylistsRequest() {
        return (requestType == DATABASE_PLAYLISTS);
    }

    /**
     * Returns <code>true</code> if this is a playlist
     * request
     *
     * <p><i>GET /databases/databaseId/containers/containerId/items HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isPlaylistSongsRequest() {
        return (requestType == PLAYLIST_SONGS);
    }

    /**
     * Returns <code>true</code> if this is a song
     * request (stream)
     *
     * <p><i>GET /databases/databaseId/items/itemId.format HTTP/1.1</i></p> 
     *
     * @return
     */
    public boolean isSongRequest() {
        return (requestType == SONG);
    }

    /**
     * Returns the URI
     *
     * @return
     */
    public URI getUri() {
        return uri;
    }

    /**
     * Returns the sessionId
     *
     * @return
     */
    public int getSessionId() {
        return sessionId;
    }

    /**
     * Returns the revision-number
     *
     * @return
     */
    public int getRevisionNumber() {
        return revisionNumber;
    }

    /**
     * What's delta? Delta is the difference between
     * the current revision of the Library (Server) 
     * and the latest revision of which iTunes (Client) 
     * knows.
     *
     * @return
     */
    public int getDelta() {
        return delta;
    }

    /**
     * Returns the keys of the requested meta data
     * as List. Note: this data isn't used to generate
     * a response. iTunes is very fussy about the return
     * order of some items and it would be to expensive 
     * bring the List into the correct order.
     *
     * @return
     */
    public List getMeta() {
        // parse only if required...
        if (meta == null && metaString != null) {
            meta = DaapUtil.parseMeta(metaString);
            metaString = null;
        }

        return meta;
    }

    /**
     * Returns the databaseId
     *
     * @return
     */
    public int getDatabaseId() {
        return databaseId;
    }

    /**
     * Returns the containerId
     *
     * @return
     */
    public int getContainerId() {
        return containerId;
    }

    /**
     * Returns the itemId
     *
     * @return
     */
    public int getItemId() {
        return itemId;
    }

    /**
     * Returns <code>true</code> if databaseId is set (i.e.
     * something else than {@see DaapUtil.UNDEF_VALUE}).
     *
     * @return
     */
    public boolean isDatabaseIdSet() {
        return (databaseId != DaapUtil.NULL);
    }

    /**
     * Returns <code>true</code> if containerId is set (i.e.
     * something else than {@see DaapUtil.UNDEF_VALUE}).
     *
     * @return
     */
    public boolean isContainerIdSet() {
        return (containerId != DaapUtil.NULL);
    }

    /**
     * Returns <code>true</code> if itemId is set (i.e.
     * something else than {@see DaapUtil.UNDEF_VALUE}).
     *
     * @return
     */
    public boolean isItemIdSet() {
        return (itemId != DaapUtil.NULL);
    }

    /**
     * Returns the raw request time.
     *
     * @return
     */
    public int getRequestType() {
        return requestType;
    }

    /**
     * Returns the query of this requests URI as
     * a Map
     *
     * @return
     */
    public Map getQueryMap() {
        return queryMap;
    }

    /**
     * Returns <code>true</code> if this is a "fake" request 
     * generated by the server. It's needed to bypass some
     * security checks of DaapRequestProcessor.
     *
     * @return
     */
    public boolean isServerSideRequest() {
        return isServerSideRequest;
    }

    /**
     * Returns <code>true</code> if this request is an update
     * request. Except for the first request it's always
     * update type request.
     *
     * @return
     */
    public boolean isUpdateType() {
        return isUpdateType;
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();

        if (isServerSideRequest)
            buffer.append("ServerSideRequest").append("\n");

        if (uri != null)
            buffer.append(uri).append("\n");

        if (headers != null) {
            for (int i = 0; i < headers.size(); i++)
                buffer.append(headers.get(i));
        }

        return buffer.toString();
    }
}