/*
 *  Copyright (C) 2010-2012,2020 Stichting Akvo (Akvo Foundation)
 *
 *  This file is part of Akvo FLOW.
 *
 *  Akvo FLOW is free software: you can redistribute it and modify it under the terms of
 *  the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,
 *  either version 3 of the License or any later version.
 *
 *  Akvo FLOW 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 Affero General Public License included below for more details.
 *
 *  The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.
 */

package com.gallatinsystems.framework.rest;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import javax.servlet.http.HttpServletRequest;

import com.gallatinsystems.framework.rest.exception.RestValidationException;

/**
 * base class for all rest api requests. It handles populating the common api attributes (action,
 * apiKey, start/endRow, maxResults). All apis do NOT need to take these params, but if they do, the
 * param names defined in this class should be used so they can be handled automatically.
 * 
 * @author Christopher Fagiani
 */
public abstract class RestRequest implements Serializable {
    private static final long serialVersionUID = -8356057661356968219L;
    public static final String ACTION_PARAM = "action";
    private static final String STARTROW_PARAM = "startRow";
    private static final String ENDROW_PARAM = "endRow";
    public static final String CURSOR_PARAM = "cursor";
    private static final String DESIRED_RESULTS_PARAM = "maxResults";
    private static final String NULL_STRING = "null";
    public static final String API_KEY_PARAM = "k";
    public static final String HASH_PARAM = "h";
    public static final String TIMESTAMP_PARAM = "ts";
    public static final String RUN_AS_TASK_PARAM = "runAsTask";
    public static final String QUEUED_TASK_HEADER = "X-Akvo-Queued-Task";

    private List<RestError> validationErrorList;

    private int startRow;
    private int endRow;
    private int desiredResults;
    private String action;
    private String apiKey;
    private String hash;
    private Date timestamp;

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }

    private String cursor;

    /**
     * populates the common fields and then dispatches ot the populateFields abstract method for
     * subclass specific handling.
     * 
     * @param servletRequest
     * @throws Exception
     */
    public void populateFromHttpRequest(HttpServletRequest servletRequest)
            throws Exception {
        setAction(servletRequest.getParameter(RestRequest.ACTION_PARAM));
        setApiKey(servletRequest.getParameter(RestRequest.API_KEY_PARAM));
        setHash(servletRequest.getParameter(RestRequest.HASH_PARAM));
        if (servletRequest.getParameter(RestRequest.TIMESTAMP_PARAM) != null) {
            try {
                DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                df.setTimeZone(TimeZone.getTimeZone("GMT"));
                setTimestamp(df.parse(servletRequest.getParameter(RestRequest.TIMESTAMP_PARAM)));
            } catch (Exception e) {
                addError(new RestError(RestError.BAD_DATATYPE_CODE,
                        RestError.BAD_DATATYPE_MESSAGE, TIMESTAMP_PARAM
                                + " must be an w3c date"));
            }
        }

        cursor = servletRequest.getParameter(CURSOR_PARAM);
        if (cursor != null) {
            if (NULL_STRING.equalsIgnoreCase(cursor.trim())
                    || cursor.trim().length() == 0) {
                cursor = null;
            }
        }
        setStartRow(stringToInt(servletRequest
                .getParameter(RestRequest.STARTROW_PARAM)));
        setEndRow(stringToInt(servletRequest
                .getParameter(RestRequest.ENDROW_PARAM)));
        setDesiredResults(stringToInt(servletRequest
                .getParameter(RestRequest.DESIRED_RESULTS_PARAM)));
        populateFields(servletRequest);
    }

    public String getCursor() {
        return cursor;
    }

    public void setCursor(String cursor) {
        this.cursor = cursor;
    }

    public List<RestError> getValidationErrorList() {
        return validationErrorList;
    }

    public int getStartRow() {
        return startRow;
    }

    public void setStartRow(int startRow) {
        this.startRow = startRow;
    }

    public int getEndRow() {
        return endRow;
    }

    public void setEndRow(int endRow) {
        this.endRow = endRow;
    }

    public int getDesiredResults() {
        return desiredResults;
    }

    public void setDesiredResults(int desiredResults) {
        this.desiredResults = desiredResults;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    private int stringToInt(String val) {
        int intVal = -1;
        if (val != null) {
            intVal = Integer.parseInt(val);
        }
        return intVal;
    }

    /**
     * adds a RestError to the internal validationError list
     * 
     * @param err
     */
    protected void addError(RestError err) {
        if (validationErrorList == null) {
            validationErrorList = new ArrayList<RestError>();
        }
        validationErrorList.add(err);
    }

    /**
     * subclasses should implement this method such that it reads all the subclass-specific values
     * from the HTTP request and sets the corresponding fields within the subclass with the values.
     * 
     * @param req
     * @throws Exception
     */
    protected abstract void populateFields(HttpServletRequest req)
            throws Exception;

    /**
     * populates any errors in the subclass-specific fields
     */
    protected abstract void populateErrors();

    /**
     * validates the request by calling populateErrors to perform subclass-specific validation.
     * 
     * @throws RestValidationException
     */
    public void validate() throws RestValidationException {
        populateErrors();
        if (validationErrorList != null && validationErrorList.size() > 0) {
            throw new RestValidationException(validationErrorList,
                    "Validation error", null);
        }
    }

    /**
     * Convenience method to parse a string value as a Long and to add a new BAD_DATATYPE error in
     * the event that its unparseable
     * 
     * @param val
     * @param field
     * @return
     */
    protected Long parseLong(String val, String field) {
        Long result = null;
        if (val != null && val.trim().length() > 0) {
            try {
                result = Long.parseLong(val);
            } catch (Exception e) {
                addError(new RestError(RestError.BAD_DATATYPE_CODE,
                        RestError.BAD_DATATYPE_MESSAGE, field
                                + " must be an integer"));
            }
        }
        return result;
    }

}

@@ -946,16 +946,81 @@
 imeZone;
+%0Aimport java.util.logging.Level;%0Aimport java.util.logging.Logger;
 %0A%0Aimport
@@ -1530,16 +1530,95 @@
 zable %7B%0A
+    private static Logger log = Logger.getLogger(RestRequest.class.getName());%0A
     priv
@@ -6931,32 +6931,123 @@
 t.size() %3E 0) %7B%0A
+            log.log(Level.SEVERE, %22Validation errors: %22 + validationErrorList.toString());%0A
             thro

Add logging for validation errors