com.splunk.ResultsReaderJson.java Source code

Java tutorial

Introduction

Here is the source code for com.splunk.ResultsReaderJson.java

Source

/*
 * Copyright 2012 Splunk, Inc.
 *
 * 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 com.splunk;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * The {@code ResultsReaderJson} class represents a streaming JSON reader for
 * Splunk search results. This class requires the gson-2.1.jar file in your 
 * build path. If you want to access the preview events, use the 
 * {@link MultiResultsReaderJson} class.
 */
public class ResultsReaderJson extends ResultsReader {
    private JsonReader jsonReader;
    // Helper object that will only be constructed if the reader is handling
    // json format used by export.
    private ExportHelper exportHelper;
    // Whether the 'preview' flag is read
    private boolean previewFlagRead;

    /**
     * Class constructor.
     *
     * Constructs a streaming JSON reader for the event stream. You should only
     * attempt to parse a JSON stream with this reader. If you attempt to parse 
     * a different type of stream, unpredictable results may occur. 
     *
     * @param inputStream The JSON stream to parse.
     * @throws IOException
     */
    public ResultsReaderJson(InputStream inputStream) throws IOException {
        this(inputStream, false);
    }

    ResultsReaderJson(InputStream inputStream, boolean isInMultiReader) throws IOException {
        super(inputStream, isInMultiReader);
        jsonReader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
        // if stream is empty, return a null reader.
        jsonReader.setLenient(true);
        if (isExportStream || isInMultiReader)
            exportHelper = new ExportHelper();
        finishInitialization();
    }

    // Advance in the json stream, reading meta data if available, and
    // get ready for readEvent method.
    // Return false if end of stream is encountered.
    boolean advanceIntoNextSetBeforeEvent() throws IOException {
        // jsonReader will be set to null once the end is reached.
        if (jsonReader == null)
            return false;

        // In Splunk 5.0 from the export endpoint,
        // each result is in its own top level object.
        // In Splunk 5.0 not from the export endpoint, the results are
        // an array at that object's key "results".
        // In Splunk 4.3, the
        // array was the top level returned. So if we find an object
        // at top level, we step into it until we find the right key,
        // then leave it in that state to iterate over.
        try {
            // Json single-reader depends on 'isExport' flag to function.
            // It does not support a stream from a file saved from
            // a stream from an export endpoint.
            // Json multi-reader assumes export format thus does not support
            // a stream from none export endpoints.
            if (exportHelper != null) {
                if (jsonReader.peek() == JsonToken.BEGIN_ARRAY)
                    throw new UnsupportedOperationException("A stream from an export endpoint of "
                            + "a Splunk 4.x server in the JSON output format " + "is not supported by this class. "
                            + "Use the XML search output format, " + "and an XML result reader instead.");
                /*
                 * We're on a stream from an export endpoint
                 * Below is an example of an input stream.
                 *      {"preview":true,"offset":0,"lastrow":true,"result":{"host":"Andy-PC","count":"62"}}
                 *      {"preview":true,"offset":0,"result":{"host":"Andy-PC","count":"1682"}}
                 */
                // Read into first result object of the next set.
                while (true) {
                    boolean endPassed = exportHelper.lastRow;
                    exportHelper.skipRestOfRow();
                    if (!exportHelper.readIntoRow())
                        return false;
                    if (endPassed)
                        break;
                }
                return true;
            }
            // Single-reader not from an export endpoint
            if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
                /*
                 * We're on Splunk 5 with a single-reader not from
                 * an export endpoint
                 * Below is an example of an input stream.
                 *     {"preview":false,"init_offset":0,"messages":[{"type":"DEBUG","text":"base lispy: [ AND index::_internal ]"},{"type":"DEBUG","text":"search context: user=\"admin\", app=\"search\", bs-pathname=\"/Users/fross/splunks/splunk-5.0/etc\""}],"results":[{"sum(kb)":"14372242.758775","series":"twitter"},{"sum(kb)":"267802.333926","series":"splunkd"},{"sum(kb)":"5979.036338","series":"splunkd_access"}]}
                 */
                jsonReader.beginObject();
                String key;
                while (true) {
                    key = jsonReader.nextName();
                    if (key.equals("preview"))
                        readPreviewFlag();
                    else if (key.equals("results")) {
                        jsonReader.beginArray();
                        return true;
                    } else {
                        skipEntity();
                    }
                }
            } else { // We're on Splunk 4.x, and we just need to start the array.
                /*
                 * Below is an example of an input stream
                 *   [
                 *       {
                 *           "sum(kb)":"14372242.758775",
                 *               "series":"twitter"
                 *       },
                 *       {
                 *           "sum(kb)":"267802.333926",
                 *               "series":"splunkd"
                 *       },
                 *       {
                 *           "sum(kb)":"5979.036338",
                 *               "series":"splunkd_access"
                 *       }
                 *   ]
                 */
                jsonReader.beginArray();
                return true;
            }
        } catch (EOFException e) {
            return false;
        }
    }

    private void readPreviewFlag() throws IOException {
        isPreview = jsonReader.nextBoolean();
        previewFlagRead = true;
    }

    /**
     * Skip the next value, whether it is atomic or compound, in the JSON
     * stream.
     */
    private void skipEntity() throws IOException {
        if (jsonReader.peek() == JsonToken.STRING) {
            jsonReader.nextString();
        } else if (jsonReader.peek() == JsonToken.BOOLEAN) {
            jsonReader.nextBoolean();
        } else if (jsonReader.peek() == JsonToken.NUMBER) {
            jsonReader.nextDouble();
        } else if (jsonReader.peek() == JsonToken.NULL) {
            jsonReader.nextNull();
        } else if (jsonReader.peek() == JsonToken.NAME) {
            jsonReader.nextName();
        } else if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
            jsonReader.beginArray();
            while (jsonReader.peek() != JsonToken.END_ARRAY) {
                skipEntity();
            }
            jsonReader.endArray();
        } else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
            jsonReader.beginObject();
            while (jsonReader.peek() != JsonToken.END_OBJECT) {
                skipEntity();
            }
            jsonReader.endObject();
        }
    }

    /** {@inheritDoc} */
    @Override
    public void close() throws IOException {
        super.close();
        if (jsonReader != null)
            jsonReader.close();
        jsonReader = null;
    }

    /** {@inheritDoc} */
    public boolean isPreview() {
        if (!previewFlagRead)
            throw new UnsupportedOperationException(
                    "isPreview() is not supported " + "with a stream from a Splunk 4.x server by this class. "
                            + "Use the XML format and an XML result reader instead.");
        return isPreview;
    }

    /**
     * This method is not supported.
     * @return Not applicable.
     */
    public Collection<String> getFields() {
        throw new UnsupportedOperationException("getFields() is not supported by this subclass.");
    }

    @Override
    Event getNextEventInCurrentSet() throws IOException {
        if (exportHelper != null) {
            // If the last row has been passed and moveToNextStreamPosition
            // has not been called, end the current set.
            if (exportHelper.lastRow && !exportHelper.inRow) {
                return null;
            }
            exportHelper.readIntoRow();
        }

        Event returnData = readEvent();

        if (exportHelper != null) {
            exportHelper.skipRestOfRow();
            return returnData;
        }
        // Single reader not from export
        if (returnData == null)
            close();
        return returnData;
    }

    private Event readEvent() throws IOException {
        Event returnData = null;
        String name = null;
        List<String> values = new ArrayList<String>();

        if (jsonReader == null)
            return null;

        // Events are almost flat, so no need for a true general parser
        // solution. But the Gson parser is a little unintuitive here. Nested
        // objects, have their own relative notion of hasNext. This
        // means that for every object or array start, hasNext() returns false
        // and one must consume the closing (END) object to get back to the
        // previous object.
        while (jsonReader.hasNext()) {
            if (returnData == null) {
                returnData = new Event();
            }
            if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
                jsonReader.beginObject();
            }
            if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
                jsonReader.beginArray();
                // The Gson parser is a little unintuitive here. Nested objects,
                // have their own relative notion of hasNext; when hasNext()
                // is done, it is only for this array.
                while (jsonReader.hasNext()) {
                    JsonToken jsonToken2 = jsonReader.peek();
                    if (jsonToken2 == JsonToken.STRING) {
                        values.add(jsonReader.nextString());
                    }
                }
                jsonReader.endArray();

                String[] valuesArray = values.toArray(new String[values.size()]);
                returnData.putArray(name, valuesArray);

                values.clear();
            }
            if (jsonReader.peek() == JsonToken.NAME) {
                name = jsonReader.nextName();
            }
            if (jsonReader.peek() == JsonToken.STRING) {
                String delimitedValues = jsonReader.nextString();
                returnData.putSingleOrDelimited(name, delimitedValues);
            }
            if (jsonReader.peek() == JsonToken.END_OBJECT) {
                jsonReader.endObject();
                break;
            }
            if (jsonReader.peek() == JsonToken.END_ARRAY) {
                jsonReader.endArray();
            }
        }
        return returnData;
    }

    @Override
    boolean advanceStreamToNextSet() throws IOException {
        return advanceIntoNextSetBeforeEvent();
    }

    /**
     * Contains code only used for streams from the export endpoint.
     */
    private class ExportHelper {
        // Initial value must be true so that
        // the first row is treated as the start of a new set.
        boolean lastRow = true;
        boolean inRow;

        ExportHelper() {
        }

        // Return false if end of stream is encountered.
        private boolean readIntoRow() throws IOException {
            if (inRow)
                return true;
            if (jsonReader.peek() == JsonToken.END_DOCUMENT)
                return false;
            inRow = true;
            jsonReader.beginObject();
            // lastrow name and value pair does not appear if the row
            // is not the last in the set.
            lastRow = false;
            while (jsonReader.hasNext()) {
                String key = jsonReader.nextName();
                if (key.equals("preview")) {
                    readPreviewFlag();
                } else if (key.equals("lastrow")) {
                    lastRow = jsonReader.nextBoolean();
                } else if (key.equals("result")) {
                    return true;
                } else {
                    skipEntity();
                }
            }
            return false;
        }

        private void skipRestOfRow() throws IOException {
            if (!inRow)
                return;
            inRow = false;
            while (jsonReader.peek() != JsonToken.END_OBJECT) {
                skipEntity();
            }
            jsonReader.endObject();
        }
    }
}