 /*
  * RHQ Management Platform
  * Copyright (C) 2005-2008 Red Hat, Inc.
  * All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2, as
  * published by the Free Software Foundation, and/or the GNU Lesser
  * General Public License, version 2.1, also as published by the Free
  * Software Foundation.
  *
  * 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 and the GNU Lesser General Public License
  * for more details.
  *
  * You should have received a copy of the GNU General Public License
  * and the GNU Lesser General Public License along with this program;
  * if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
package org.rhq.plugins.platform.content.yum;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Represents a yum/http request made by a yum client.
 *
 * @author jortel
 */
class Request {
    /**
     * The yum server.
     */
    final YumServer server;

    /**
     * An open client (accepted socket)
     */
    private final Socket socket;

    /**
     * A yum repomd metadata object.
     */
    private final Repomd repomd;

    /**
     * A current yum primary metadata object.
     */
    private final Primary primary;

    /**
     * The http <i>filename</i> contained in the URL for the request.
     */
    String filename;

    /**
     * The http <i>args</i> contained in the URL for the request.
     */
    Map<String, String> args;

    /**
     * The http <i>header</i> fields.
     */
    Map<String, String> fields;

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

    /**
     * Constructed with the server and client socket.
     *
     * @param server The yum server that accepted the request.
     * @param socket An open/conditioned client socket.
     */
    Request(YumServer server, Socket socket) {
        this.server = server;
        this.socket = socket;
        repomd = new Repomd(this);
        primary = new Primary(this);
    }

    /**
     * Returns the current yum context.
     *
     * @return A yum context.
     */
    YumContext context() {
        return server.context;
    }

    /**
     * Process the yum (http) request. This class provides bare-bones http request processing.
     *
     * <p/>* Read and process the header into a map of header fields and args..
     *
     * <p/>* Reply to the request.
     */
    void process() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String request = reader.readLine();
            if (request!=null) {
                StringTokenizer st = new StringTokenizer(request);
                st.nextToken(); // skip request-type
                String url = st.nextToken();
                log.info("processing: " + url);
                String path = url.substring(context().basepath().length() + 1);
                int qmark = qmark(path);
                filename = path.substring(0, qmark);
                args = getArgs(path.substring(qmark));
                fields = httpFields(reader);
                reply();
            }
        } catch (Exception e) {
            log.error("request failed:", e);
        } finally {
            close(socket);
        }
    }

    /**
     * Clean the metadata. Delete locally cached metadata files.
     */
    void cleanMetadata() {
        repomd.delete();
        primary.delete();
    }

    /**
     * Return the index of the (?) character in the URL.
     *
     * @param  path A URL path string.
     *
     * @return The index when found, else the string length.
     */
    private int qmark(String path) {
        int qmark = path.indexOf('?');
        return ((qmark == -1) ? path.length() : qmark);
    }

    /**
     * Build and return a map containing the <i>args</i> part of the URL string.
     *
     * @param  path The path segment of the URL string.
     *
     * @return A map containing the URL args.
     */
    private Map<String, String> getArgs(String path) {
        if (path.length() < 4) {
            return Collections.emptyMap();
        }

        path = path.substring(1);
        Map<String, String> result = new HashMap<String, String>();
        for (String arg : path.split(",")) {
            int eq = arg.indexOf("=");
            String name = arg.substring(0, eq);
            String value = arg.substring(eq + 1);
            result.put(name.trim(), value.trim());
        }

        return result;
    }

    /**
     * Reply to the request using the appropriate content object.
     *
     * @throws Exception On all errors.
     */
    private void reply() throws Exception {
        Content content = selectContent(filename);
        if (content == null) {
            content = new Package(this);
        }

        OutputStream ostr = socket.getOutputStream();
        content.writeHeader(ostr);
        content.writeContent(ostr);
        ostr.close();
    }

    /**
     * Select the appropriate content object based on the file specified in the request URL. Selects a metadata object
     * when identified by name, else it is assumed that the cotent is a package request.
     *
     * @param  filename The request protion of the URL string.
     *
     * @return The content object specified by name in the request.
     */
    private Content selectContent(String filename) {
        if (filename.equals("repodata/repomd.xml")) {
            repomd.delete();
            if (primary.stale()) {
                primary.delete();
            }

            return repomd;
        }

        if (filename.equals("repodata/primary.xml")) {
            return primary;
        }

        return null;
    }

    /**
     * Process the http request using the specified reader and build a map containing the names and values of all of the
     * http header fields.
     *
     * @param  reader A reader opened on the http request input stream.
     *
     * @return A map containing the http header fields.
     *
     * @throws Exception On all errors.
     */
    private Map<String, String> httpFields(BufferedReader reader) throws Exception {
        Map<String, String> result = new HashMap<String, String>();
        while (true) {
            String line = reader.readLine();
            if (line!= null && line.length() > 0) {
                String[] pair = line.split(":");
                result.put(pair[0], pair[1].trim());
            } else {
                break;
            }
        }

        return result;
    }

    /**
     * Quietly close the specified socket. Mitigates the <u>really</u> annoying pattern by <i>java.net/java.io</i>
     * objects that throw an exception on close() operations which <u>really</u> sucks when trying to close resources in
     * the finally clause.
     *
     * @param socket An open socket.
     */
    void close(Socket socket) {
        try {
            socket.close();
        } catch (Exception e) {
            log.error("close", e);
        }
    }
}