org.ardverk.daap.DaapRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.ardverk.daap.DaapRequest.java

Source

/*
 * Digital Audio Access Protocol (DAAP) Library
 * Copyright (C) 2004-2010 Roger Kapsi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ardverk.daap;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import javax.print.URIException;

import org.apache.http.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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 {

    public static final String AUTHORIZATION = "Authorization";
    public static final String CLIENT_DAAP_VERSION = "Client-DAAP-Version";
    public static final String USER_AGENT = "User-Agent";

    /** "/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 Logger LOG = LoggerFactory.getLogger(DaapRequest.class);

    private URI uri;

    private Map<String, String> queryMap;

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

    private List<String> meta;
    private String metaString;

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

    private List<Header> headers;
    private boolean isServerSideRequest;
    private boolean isUpdateType;

    private DaapConnection connection;

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

    /**
     * Creates a server side fake update DaapRequest to issue an update
     * 
     * @param sessionId
     * @param revisionNumber
     * @param delta
     */
    public DaapRequest(DaapConnection connection, SessionId 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 URISyntaxException {
        this(connection);

        URI uri = null;

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

            try {
                uri = new URI(st.nextToken());
            } catch (URISyntaxException err) {
                LOG.error("URISyntaxException", err);
            }

            st.nextToken(); // protocol
        } catch (NoSuchElementException err) {
            LOG.error("NoSuchElementException", err);
        }

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

        setURI(uri);
    }

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

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

        setURI(uri);
    }

    /**
     * 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 URISyntaxException {

        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 = SessionId.parseSessionId(queryMap.get("session-id"));
            }

            if (!SessionId.INVALID.equals(sessionId)) {

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

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

                if (delta > revisionNumber) {
                    throw new URISyntaxException(uri.toASCIIString(),
                            "Delta must be less or equal to revision-number: " + delta + "/" + revisionNumber);
                }

                if (queryMap.containsKey("meta")) {
                    metaString = 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 URISyntaxException(uri.toASCIIString(),
                                    "Unknown token in path: " + path + " [" + token + "]@1");
                        }

                        databaseId = DaapUtil.parseUInt(tok.nextToken());
                        token = tok.nextToken();

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

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

                        } else if (count == 4) {

                            token = tok.nextToken();

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

                            if (fileTokenizer.countTokens() == 2) {
                                itemId = DaapUtil.parseUInt(fileTokenizer.nextToken());
                                requestType = SONG;

                            } else {
                                throw new URISyntaxException(uri.toASCIIString(),
                                        "Unknown token in path: " + path + " [" + token + "]@3");
                            }

                        } else if (count == 5) {
                            containerId = DaapUtil.parseUInt(tok.nextToken());
                            token = tok.nextToken();

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

                            } else {
                                throw new URISyntaxException(uri.toASCIIString(),
                                        "Unknown token in path: " + path + " [" + token + "@4");
                            }

                        } else {
                            throw new URISyntaxException(uri.toASCIIString(),
                                    "Unknown token in path: " + path + " [" + token + "]@5");
                        }
                    } else {
                        throw new URISyntaxException(uri.toASCIIString(), "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 = SessionId.INVALID;
            revisionNumber = DaapUtil.NULL;
            delta = DaapUtil.NULL;
        }
    }

    public void setSessionId(SessionId sessionId) {
        this.sessionId = sessionId;
    }

    /**
     * Adds an array of Headers to this requests list of Headers
     * 
     * @return
     */
    public void addHeaders(Header[] headers) {
        for (Header header : headers) {
            this.headers.add(header);
        }
    }

    /**
     * Adds a list of headers to this requests list
     * 
     * @return
     */
    public void addHeaders(List<? extends Header> 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<Header> 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;
        }

        for (Header header : headers) {
            if (header.getName().equals(key)) {
                return header;
            }
        }
        return null;
    }

    /**
     * Returns the Server reference
     */
    public DaapServer<?> getServer() {
        return getConnection().getServer();
    }

    /**
     * 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 SessionId 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<String> getMeta() {
        // parse only if required...
        if (meta == null && metaString != null) {
            meta = DaapUtil.parseMeta(metaString);
            metaString = null;
        }

        if (meta != null) {
            return Collections.unmodifiableList(meta);
        }

        return Collections.emptyList();
    }

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

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

    /**
     * Returns the itemId
     * 
     * @return
     */
    public long 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<String, String> getQueryMap() {
        if (queryMap != null) {
            return Collections.unmodifiableMap(queryMap);
        }

        return Collections.emptyMap();
    }

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

    /**
     * Returns <code>true</code> if client accepts GZIP encoding.
     * 
     * @return
     */
    public boolean isGZIPSupported() {
        Header header = getHeader("Accept-Encoding");
        return header != null && header.getValue().equalsIgnoreCase("gzip");
    }

    public boolean isKeepConnectionAlive() {
        Header header = getHeader("Connection");
        return header != null && header.getValue().equalsIgnoreCase("keep-alive");
    }

    /**
     * 
     * @return
     */
    public Library getLibrary() {
        return connection.getServer().getLibrary();
    }

    /**
     * 
     * @return
     */
    public Library getHeadLibrary() {
        return connection.getFirstInQueue();
    }

    /**
     * 
     * @return
     */
    public Library nextLibrary() {
        return connection.nextLibrary(this);
    }

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

        if (isServerSideRequest)
            buffer.append("ServerSideRequest: ").append(getRevisionNumber()).append(", ").append(getDelta())
                    .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();
    }
}