/*
* Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.flashback.serialization;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.linkedin.flashback.scene.Scene;
import com.linkedin.flashback.serializable.RecordedByteHttpBody;
import com.linkedin.flashback.serializable.RecordedEncodedHttpBody;
import com.linkedin.flashback.serializable.RecordedHttpBody;
import com.linkedin.flashback.serializable.RecordedHttpExchange;
import com.linkedin.flashback.serializable.RecordedHttpRequest;
import com.linkedin.flashback.serializable.RecordedHttpResponse;
import com.linkedin.flashback.serializable.RecordedStringHttpBody;
import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class is used for writing, reading scene and de-serializing{@link com.linkedin.flashback.scene.Scene}
* Explicit serialization via Streaming API: http://wiki.fasterxml.com/JacksonInFiveMinutes
* @author shfeng
* @author dvinegra
*/
public class SceneDeserializer {
private static final JsonFactory JSON_FACTORY = new JsonFactory();
private JsonParser _jsonParser;
public Scene deserialize(Reader reader)
throws IOException {
_jsonParser = JSON_FACTORY.createParser(reader);
skipStartObject();
_jsonParser.nextToken();
validateRequiredField(SceneSerializationConstant.SCENE_TAG_NAME);
_jsonParser.nextToken();
String name = _jsonParser.getValueAsString();
return new Scene(name, null, ".", createHttpExchangeList());
}
private List<RecordedHttpExchange> createHttpExchangeList()
throws IOException {
_jsonParser.nextToken(); //HTTPEXCHANGELIST FIELD_NAME
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPEXCHANGE_LIST);
skipStartObject(); // HTTPEXCHANGELIST START_OBJECT
List<RecordedHttpExchange> recordedHttpExchangeList = new ArrayList<>();
int count = 1;
_jsonParser.nextToken(); // HTTPEXCHANGE FIELD_NAME
while (_jsonParser.getCurrentToken() != JsonToken.END_OBJECT) {
if ((SceneSerializationConstant.SCENE_TAG_HTTPEXCHANGE + count).equals(_jsonParser.getCurrentName())) {
skipStartObject();
recordedHttpExchangeList.add(createHttpExchange());
count++;
skipEndObject(); // JsonToken.START_OBJECT
}
}
return recordedHttpExchangeList;
}
private RecordedHttpExchange createHttpExchange()
throws IOException {
_jsonParser.nextToken(); // UPDATETIME FIELD_NAME
validateRequiredField(SceneSerializationConstant.SCENE_TAG_UPDATE_TIME);
_jsonParser.nextToken(); // UPDATETIME VALUE_STRING
Date date = new Date(_jsonParser.getValueAsString());
RecordedHttpRequest recordedHttpRequest = createHttpRequest();
RecordedHttpResponse recordedHttpResponse = createHttpResponse();
skipEndObject(); // HTTPEXCHANGE
return new RecordedHttpExchange(recordedHttpRequest, recordedHttpResponse, date);
}
private RecordedHttpRequest createHttpRequest()
throws IOException {
_jsonParser.nextToken(); // HTTPREQUEST.FIELD_NAME
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPREQUEST);
_jsonParser.nextToken(); // HTTPREQUEST START_OBJECT
String httpMethod = createHttpMethod();
URI uri = createUri();
//Create optional fields
_jsonParser.nextToken(); // Move to next token
Map<String, String> headers = createHeaders();
RecordedHttpBody recordedHttpBody = createHttpBody();
RecordedHttpRequest recordedHttpRequest = new RecordedHttpRequest(httpMethod, uri, headers, recordedHttpBody);
//Check if reach to the end of object for http request
if (!(_jsonParser.getCurrentName().equals(SceneSerializationConstant.SCENE_TAG_HTTPREQUEST)
&& _jsonParser.getCurrentToken() == JsonToken.END_OBJECT)) {
_jsonParser.nextToken();
}
return recordedHttpRequest;
}
private URI createUri()
throws IOException {
_jsonParser.nextToken(); // HTTPURI FIELD_NAME
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPURI);
_jsonParser.nextToken(); // HTTPURI VALUE_STRING
URI uri;
try {
uri = new URI(_jsonParser.getValueAsString());
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to construct URI: " + _jsonParser.getValueAsString());
}
return uri;
}
private String createHttpMethod()
throws IOException {
_jsonParser.nextToken(); // HTTPMETHOD FIELD_NAME
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPMETHOD);
_jsonParser.nextToken(); // HTTPMETHOD VALUE_STRING
return _jsonParser.getValueAsString();
}
private RecordedHttpResponse createHttpResponse()
throws IOException {
_jsonParser.nextToken(); // HTTPRESPONSE Field name
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPRESPONSE);
_jsonParser.nextToken(); // HTTPRESPONSE.START_OBJECT
int statusCode = createStatusCode();
_jsonParser.nextToken(); // Move to next token
Map<String, String> headers = createHeaders();
RecordedHttpBody recordedHttpBody = createHttpBody();
RecordedHttpResponse recordedHttpResponse = new RecordedHttpResponse(statusCode, headers, recordedHttpBody);
//Check if reach to the end of object for http response
if (!(SceneSerializationConstant.SCENE_TAG_HTTPRESPONSE.equals(_jsonParser.getCurrentName())
&& _jsonParser.getCurrentToken() == JsonToken.END_OBJECT)) {
_jsonParser.nextToken();
}
return recordedHttpResponse;
}
private int createStatusCode()
throws IOException {
_jsonParser.nextToken(); // HTTPSTATUSCODE Filed
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPSTATUS_CODE);
_jsonParser.nextToken(); //HTTPSTATUSCODE VALUE_NUMBER_INT
return _jsonParser.getValueAsInt();
}
private Map<String, String> createHeaders()
throws IOException {
if (!isValidOptionalField(SceneSerializationConstant.SCENE_TAG_HTTPHEADERS)) {
return null;
}
skipStartObject(); // HTTPHEADERS
Map<String, String> headers = new HashMap<>();
while (_jsonParser.nextToken() != JsonToken.END_OBJECT) { //FIELD_NAME
String key = _jsonParser.getCurrentName();
_jsonParser.nextToken(); // VALUE_STRING
String value = _jsonParser.getValueAsString();
headers.put(key, value);
}
if (SceneSerializationConstant.SCENE_TAG_HTTPHEADERS.equals(_jsonParser.getCurrentName())
&& _jsonParser.getCurrentToken() == JsonToken.END_OBJECT) {
skipEndObject();
}
return headers;
}
private RecordedHttpBody createHttpBody()
throws IOException {
if (isValidOptionalField(SceneSerializationConstant.SCENE_TAG_ENCODED_HTTPBODY)) {
skipStartObject(); // ENCODEDHTTPBODY
_jsonParser.nextToken(); // HTTPBODYENCODING Field
validateRequiredField(SceneSerializationConstant.SCENE_TAG_HTTPBODY_ENCODING);
_jsonParser.nextToken(); // HTTPBODYENCODING Value
String encodingName = _jsonParser.getValueAsString();
_jsonParser.nextToken();
RecordedHttpBody decodedBody = createHttpBody(); // Read in the "decoded" body content so that we can wrap it
skipEndObject();
return new RecordedEncodedHttpBody(decodedBody, encodingName);
}
if (isValidOptionalField(SceneSerializationConstant.SCENE_TAG_STRING_HTTPBODY)) {
_jsonParser.nextToken(); // Field
return new RecordedStringHttpBody(_jsonParser.getValueAsString());
}
if (isValidOptionalField(SceneSerializationConstant.SCENE_TAG_BINARY_HTTPBODY)) {
_jsonParser.nextToken(); // Field
return new RecordedByteHttpBody(_jsonParser.getBinaryValue());
}
return null;
}
private void validateRequiredField(String expectedFiledName)
throws IOException {
if (!expectedFiledName.equals(_jsonParser.getCurrentName())) {
throw new IllegalStateException("Unrecognized field '" + _jsonParser.getCurrentName() + "'!");
}
}
private boolean isValidOptionalField(String expectedFiledName)
throws IOException {
return expectedFiledName.equals(_jsonParser.getCurrentName());
}
private void skipStartObject()
throws IOException {
_jsonParser.nextToken(); // JsonToken.START_OBJECT
}
private void skipEndObject()
throws IOException {
_jsonParser.nextToken(); // JsonToken.END_OBJECT
}
}