Java tutorial
/* * 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(); } } }