com.linuxbox.enkive.web.StatsServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.linuxbox.enkive.web.StatsServlet.java

Source

/*******************************************************************************
 * Copyright 2013 The Linux Box Corporation.
 * 
 * This file is part of Enkive CE (Community Edition).
 * 
 * Enkive CE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 * 
 * Enkive CE 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 for more details.
 * 
 * You should have received a copy of the GNU Affero General Public
 * License along with Enkive CE. If not, see
 * <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.linuxbox.enkive.web;

import static com.linuxbox.enkive.search.Constants.NUMERIC_SEARCH_FORMAT;
import static com.linuxbox.enkive.statistics.StatsConstants.STAT_GATHERER_NAME;
import static com.linuxbox.enkive.statistics.StatsConstants.STAT_INTERVAL;
import static com.linuxbox.enkive.statistics.StatsConstants.STAT_TIMESTAMP;
import static com.linuxbox.enkive.statistics.VarsMaker.createLinkedListOfStrs;
import static com.linuxbox.enkive.statistics.VarsMaker.createListOfMaps;
import static com.linuxbox.enkive.statistics.VarsMaker.createMap;
import static com.linuxbox.enkive.statistics.consolidation.ConsolidationConstants.CONSOLIDATION_MAX;
import static com.linuxbox.enkive.statistics.consolidation.ConsolidationConstants.CONSOLIDATION_MIN;
import static com.linuxbox.enkive.statistics.consolidation.ConsolidationConstants.CONSOLIDATION_TYPE;
import static com.linuxbox.enkive.statistics.gathering.mongodb.MongoConstants.MONGO_ID;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.linuxbox.enkive.exception.CannotRetrieveException;
import com.linuxbox.enkive.statistics.ConsolidationKeyHandler;
import com.linuxbox.enkive.statistics.RawStats;
import com.linuxbox.enkive.statistics.services.StatsClient;
import com.linuxbox.enkive.statistics.services.retrieval.StatsFilter;
import com.linuxbox.enkive.statistics.services.retrieval.StatsQuery;
import com.linuxbox.enkive.statistics.services.retrieval.mongodb.MongoStatsFilter;
import com.linuxbox.enkive.statistics.services.retrieval.mongodb.MongoStatsQuery;

/**
 * FIXME: This class is a mess full of NOAHCODE.
 */
@SuppressWarnings("unchecked")
public class StatsServlet extends EnkiveServlet {
    protected static final Log LOGGER = LogFactory.getLog("com.linuxbox.enkive.web.StatsServlet");
    private static final long serialVersionUID = 7062366416188559812L;

    private StatsClient client;

    private final static String tsMax = STAT_TIMESTAMP + "." + CONSOLIDATION_MAX;
    private final static String tsMin = STAT_TIMESTAMP + "." + CONSOLIDATION_MIN;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        client = getStatsClient();
    }

    private Map<String, Object> consolidateMaps(List<Map<String, Object>> serviceData,
            List<ConsolidationKeyHandler> statKeys) {
        if (serviceData.size() > 0) {
            Map<String, Object> template = createMap(serviceData.iterator().next());
            LinkedList<String> path = createLinkedListOfStrs();
            Map<String, Object> result = createMap(template);
            consolidateMapsHelper(template, result, path, statKeys, serviceData);
            result.remove(STAT_GATHERER_NAME);
            result.remove(STAT_TIMESTAMP);
            result.remove(MONGO_ID);
            return result;
        }
        return null;
    }

    private void consolidateMapsHelper(Map<String, Object> templateData, Map<String, Object> consolidatedMap,
            LinkedList<String> path, List<ConsolidationKeyHandler> statKeys,
            List<Map<String, Object>> serviceData) {
        for (String key : templateData.keySet()) {
            path.addLast(key);
            ConsolidationKeyHandler matchingConsolidationDefinition = findMatchingPath(path, statKeys);
            if (matchingConsolidationDefinition != null) {
                TreeSet<Map<String, Object>> dataSet = new TreeSet<Map<String, Object>>(new NumComparator());
                for (Map<String, Object> dataMap : serviceData) {
                    Map<String, Object> dataVal = createMap(getDataVal(dataMap, path));
                    if (dataVal != null && !dataVal.isEmpty()) {
                        dataVal.put(STAT_TIMESTAMP, dataMap.get(STAT_TIMESTAMP));
                        dataSet.add(dataVal);
                    }
                }
                putOnPath(path, consolidatedMap, dataSet);
            } else {
                if (templateData.get(key) instanceof Map) {
                    consolidateMapsHelper((Map<String, Object>) templateData.get(key), consolidatedMap, path,
                            statKeys, serviceData);
                }
            }
            path.removeLast();
        }
    }

    static class NumComparator implements Comparator<Map<String, Object>> {
        protected Object getDataVal(Map<String, Object> dataMap, List<String> path) {
            Object map = dataMap;
            for (String key : path) {
                if (((Map<String, Object>) map).containsKey(key)) {
                    if (((Map<String, Object>) map).get(key) instanceof Map) {
                        map = ((Map<String, Object>) map).get(key);
                    } else {
                        return ((Map<String, Object>) map).get(key);
                    }
                }
            }
            return null;
        }

        // Invariant: date ranges will never be the same
        @Override
        public int compare(Map<String, Object> o1, Map<String, Object> o2) {
            List<String> path = new LinkedList<String>();
            path.add(STAT_TIMESTAMP);
            Map<String, Object> dateRange1 = (Map<String, Object>) getDataVal(o1, path);
            Map<String, Object> dateRange2 = (Map<String, Object>) getDataVal(o1, path);

            if (getDataVal(o1, path) != null && getDataVal(o2, path) != null) {
                // if both maps have a num field, order them numerically
                final Long min1 = ((Date) dateRange1.get(CONSOLIDATION_MIN)).getTime();
                final Long min2 = ((Date) dateRange2.get(CONSOLIDATION_MIN)).getTime();

                // tricky way of returning negative if i1<i2, positive if i1>i2,
                // and 0 if the same
                if (min1 < min2) {
                    return -1;
                } else if (min1 > min2) {
                    return 1;
                } else {
                    Long max1 = ((Date) dateRange1.get(CONSOLIDATION_MAX)).getTime();
                    Long max2 = ((Date) dateRange2.get(CONSOLIDATION_MAX)).getTime();
                    if (max1 < max2) {
                        return -1;
                    } else if (max1 > max2) {
                        return 1;
                    } else {// should never happen
                        LOGGER.warn("numComparator attempting to store objects with same timestamp");
                        return 0;
                    }
                }
            } else if (getDataVal(o1, path) != null) {
                // if only the first map has a num field, put it first
                return -1;
            } else if (getDataVal(o2, path) != null) {
                // if only the second map has a num field, put it first
                return 1;
            } else {
                // if neither map has a num field, put second after first
                return 1;
            }
        }
    }

    // these functions are from the abstract gatherer function just
    // repurposed to do the consolidation to an array instead of combining
    // the values & reinserting.
    protected Map<String, Object> getDataVal(Map<String, Object> dataMap, List<String> path) {
        Map<String, Object> map = dataMap;
        for (String key : path) {
            if (map.containsKey(key)) {
                if (map.get(key) instanceof Map) {
                    map = (Map<String, Object>) map.get(key);
                } else {
                    return null;
                }
            }
        }
        return map;
    }

    protected void putOnPath(List<String> path, Map<String, Object> statsData, Object dataToAdd) {
        Map<String, Object> cursor = statsData;
        int index = 0;
        for (String key : path) {
            if (index == path.size() - 1) {
                cursor.put(key, dataToAdd);
            } else if (cursor.containsKey(key)) {
                if (cursor.get(key) instanceof Map) {
                    cursor = (Map<String, Object>) cursor.get(key);
                } else {
                    // TODO create path that does not exist
                    LOGGER.error("Cannot put data on path");
                }
            }
            index++;
        }
    }

    private ConsolidationKeyHandler findMatchingPath(List<String> path, List<ConsolidationKeyHandler> keys) {
        for (ConsolidationKeyHandler def : keys) {// get one key definition
            if (def.getMethods() == null) {
                continue;
            }
            boolean isMatch = true;
            int pathIndex = 0;
            int defIndex = 0;
            String keyStr;
            String pathStr;
            List<String> keyString = def.getKey();
            if (keyString.size() > path.size()) {
                isMatch = false;
                continue;
            }

            while (pathIndex < path.size()) {// run through it to compare to
                // path
                if (defIndex >= keyString.size()) {
                    isMatch = false;
                    break;
                }

                while (keyString.get(defIndex).equals("*") && defIndex < keyString.size()) {
                    if (defIndex == keyString.size() - 1) {
                        if (keyString.get(defIndex).equals("*")) {
                            if (defIndex == path.size() - 1) {
                                return def;
                            } else {
                                isMatch = false;
                                break;
                            }
                        }
                    } else {
                        defIndex++;
                        pathIndex++;
                        keyStr = keyString.get(defIndex);
                        pathStr = path.get(pathIndex);
                    }
                }
                if (pathIndex >= path.size()) {
                    isMatch = false;
                    break;
                }

                keyStr = keyString.get(defIndex);
                pathStr = path.get(pathIndex);

                if (keyStr.equals(pathStr)) {
                    pathIndex++;
                    defIndex++;
                } else {
                    isMatch = false;
                    break;
                }
            }
            if (isMatch) {
                return def;
            }
        }
        return null;
    }

    // Above is copied from abstract/embedded grainularity classes
    public void doGet(HttpServletRequest req, HttpServletResponse resp) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("StatsServlet doGet started");
        }

        try {
            try {
                Date endTimestamp = null;
                Date startTimestamp = null;
                boolean noDate = true;
                // Get a DateRange for ts.min & ts.max
                if (req.getParameter(tsMax) != null) {
                    noDate = false;
                    if (!req.getParameter(tsMax).equals("")) {
                        try {
                            endTimestamp = new Date(NUMERIC_SEARCH_FORMAT.parse(req.getParameter(tsMax)).getTime()
                                    + 1000 * 60 * 60 * 24);
                        } catch (ParseException e) {
                            endTimestamp = new Date();
                            LOGGER.error("Error Parsing Date: " + req.getParameter(tsMax), e);
                        }
                    }
                } else {
                    endTimestamp = new Date();
                }
                if (req.getParameter(tsMin) != null) {
                    noDate = false;

                    if (!req.getParameter(tsMin).equals("")) {
                        try {
                            startTimestamp = NUMERIC_SEARCH_FORMAT.parse(req.getParameter(tsMin));
                        } catch (ParseException e) {
                            startTimestamp = new Date(0L);
                            LOGGER.error("Error Parsing Date: " + req.getParameter(tsMin), e);
                        }
                    }
                } else {
                    startTimestamp = new Date(0L);
                }

                String[] serviceNames = req.getParameterValues(STAT_GATHERER_NAME);
                Integer grainType = null;

                if (req.getParameter(CONSOLIDATION_TYPE) != null) {
                    grainType = Integer.parseInt(req.getParameter(CONSOLIDATION_TYPE));
                }
                List<StatsQuery> queryList = null;
                List<StatsFilter> filterList = null;

                if (serviceNames == null) {
                    LOGGER.error("no valid data input", new NullPointerException());
                }

                if (serviceNames != null) {
                    queryList = new LinkedList<StatsQuery>();
                    filterList = new LinkedList<StatsFilter>();
                    for (String serviceName : serviceNames) {

                        // building query
                        StatsQuery query = new MongoStatsQuery(serviceName, grainType, STAT_INTERVAL,
                                startTimestamp, endTimestamp);
                        // TODO
                        StatsFilter filter = null;
                        String[] keys = req.getParameterValues(serviceName);
                        // building filter
                        if (keys != null) {
                            List<String> temp = new ArrayList<String>(Arrays.asList(keys));
                            filter = new MongoStatsFilter(serviceName, temp);
                        } else {
                            filter = new MongoStatsFilter(serviceName, null);
                        }
                        queryList.add(query);
                        filterList.add(filter);
                    }
                }

                List<Map<String, Object>> result = null;

                if (noDate) {// no date range means get instant data
                    Map<String, List<String>> gatheringStats = new HashMap<String, List<String>>();
                    for (StatsFilter tempFilter : filterList) {
                        if (tempFilter.keys != null) {
                            List<String> keys = new LinkedList<String>(tempFilter.keys.keySet());
                            gatheringStats.put(tempFilter.gathererName, keys);
                        } else {
                            gatheringStats.put(tempFilter.gathererName, null);
                        }
                    }
                    List<RawStats> tempRawStats = client.gatherData(gatheringStats);
                    result = createListOfMaps();
                    for (RawStats stats : tempRawStats) {
                        Map<String, Object> statsMap = stats.toMap();
                        result.add(statsMap);
                    }
                } else {// output query data as formatted json
                    List<Map<String, Object>> stats = client.queryStatistics(queryList, filterList);
                    result = createListOfMaps();
                    for (String name : serviceNames) {
                        List<Map<String, Object>> serviceStats = createListOfMaps();
                        // populate service data
                        for (Map<String, Object> data : stats) {
                            if (data.get(STAT_GATHERER_NAME).equals(name)) {
                                serviceStats.add(data);
                            }
                        }
                        Map<String, Object> consolidatedMap = createMap();
                        consolidatedMap.put(name,
                                consolidateMaps(serviceStats, client.getAttributes(name).getKeys()));
                        result.add(consolidatedMap);
                    }
                }

                try {
                    // 6. return data from query
                    JSONObject statistics = new JSONObject();

                    statistics.put("results", new JSONArray(result.toArray()));
                    LOGGER.debug("Statistical Data: " + statistics);
                    resp.getWriter().write(statistics.toString());
                } catch (IOException e) {
                    // FIXME: NOAHCODE Why is this calling respondError and
                    // throwing an exception, the catch of which calls
                    // respondError again?
                    respondError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, resp);
                    throw new CannotRetrieveException("could not create JSON for message attachment", e);
                } catch (JSONException e) {
                    // FIXME: NOAHCODE Why is this calling respondError and
                    // throwing an exception, the catch of which calls
                    // respondError again?
                    respondError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, resp);
                    throw new CannotRetrieveException("could not create JSON for message attachment", e);
                }
            } catch (CannotRetrieveException e) {
                respondError(HttpServletResponse.SC_UNAUTHORIZED, null, resp);
                LOGGER.error("CannotRetrieveException", e);
            } catch (NullPointerException e) {
                respondError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, resp);
                LOGGER.error("NullException thrown", e);
            }
        } catch (IOException e) {
            LOGGER.error("IOException thrown", e);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("StatsServlet doGet finished");
        }
    }
}