/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.util;
import org.apache.hadoop.corona.CoronaConf;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.impl.DefaultPrettyPrinter;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class CoronaSerializer {
/**
* The JsonParser instance contained within a CoronaSerializer instance.
*/
public JsonParser jsonParser;
/**
* Constructor for the CoronaSerializer class.
*
* @param conf The CoronaConf instance to be used
* @throws IOException
*/
public CoronaSerializer(CoronaConf conf) throws IOException {
InputStream inputStream = new FileInputStream(conf.getCMStateFile());
if (conf.getCMCompressStateFlag()) {
inputStream = new GZIPInputStream(inputStream);
}
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
JsonFactory jsonFactory = new JsonFactory();
jsonFactory.setCodec(mapper);
jsonParser = jsonFactory.createJsonParser(inputStream);
}
/**
* This is a helper method that reads a JSON token using a JsonParser
* instance, and throws an exception if the next token is not START_OBJECT.
*
* @param parentFieldName The name of the field
* @throws IOException
*/
public void readStartObjectToken(String parentFieldName)
throws IOException {
readToken(parentFieldName, JsonToken.START_OBJECT);
}
/**
* This is a helper method that reads a JSON token using a JsonParser
* instance, and throws an exception if the next token is not START_ARRAY.
*
* @param parentFieldName The name of the field
* @throws IOException
*/
public void readStartArrayToken(String parentFieldName)
throws IOException {
readToken(parentFieldName, JsonToken.START_ARRAY);
}
/**
* This is a helper method that reads a JSON token using a JsonParser
* instance, and throws an exception if the next token is not END_OBJECT.
*
* @param parentFieldName The name of the field
* @throws IOException
*/
public void readEndObjectToken(String parentFieldName)
throws IOException {
readToken(parentFieldName, JsonToken.END_OBJECT);
}
/**
* This is a helper method that reads a JSON token using a JsonParser
* instance, and throws an exception if the next token is not END_ARRAY.
*
* @param parentFieldName The name of the field
* @throws IOException
*/
public void readEndArrayToken(String parentFieldName)
throws IOException {
readToken(parentFieldName, JsonToken.END_ARRAY);
}
/**
* This is a helper method that reads a JSON token using a JsonParser
* instance, and throws an exception if the next token is not the same as
* the token we expect.
*
* @param parentFieldName The name of the field
* @param expectedToken The expected token
* @throws IOException
*/
public void readToken(String parentFieldName, JsonToken expectedToken)
throws IOException {
JsonToken currentToken = jsonParser.nextToken();
if (currentToken != expectedToken) {
throw new IOException("Expected a " + expectedToken.toString() +
" token when reading the value of the field: " +
parentFieldName +
" but found a " +
currentToken.toString() + " token");
}
}
/**
* This is a helper method which creates a JsonGenerator instance, for writing
* the state of the ClusterManager to the state file. The JsonGenerator
* instance writes to a compressed file if we have the compression flag
* turned on.
*
* @param conf The CoronaConf instance to be used
* @return The JsonGenerator instance to be used
* @throws IOException
*/
public static JsonGenerator createJsonGenerator(CoronaConf conf)
throws IOException {
OutputStream outputStream = new FileOutputStream(conf.getCMStateFile());
if (conf.getCMCompressStateFlag()) {
outputStream = new GZIPOutputStream(outputStream);
}
ObjectMapper mapper = new ObjectMapper();
JsonGenerator jsonGenerator =
new JsonFactory().createJsonGenerator(outputStream, JsonEncoding.UTF8);
jsonGenerator.setCodec(mapper);
if (!conf.getCMCompressStateFlag()) {
jsonGenerator.setPrettyPrinter(new DefaultPrettyPrinter());
}
return jsonGenerator;
}
/**
* This is a helper method, which is used to throw an exception when we
* encounter an unexpected field name.
*
* @param fieldName Name of the field
* @param expectedFieldName Name of the expected field
* @throws IOException
*/
public void foundUnknownField(String fieldName,
String expectedFieldName)
throws IOException {
throw new IOException("Found an unexpected field: " + fieldName +
", instead of field: " + expectedFieldName);
}
/**
* The method reads a field from the JSON stream, and checks if the
* field read is the same as the expect field.
*
* @param expectedFieldName The field name which is expected next
* @throws IOException
*/
public void readField(String expectedFieldName) throws IOException {
readToken(expectedFieldName, JsonToken.FIELD_NAME);
String fieldName = jsonParser.getCurrentName();
if (!fieldName.equals(expectedFieldName)) {
foundUnknownField(fieldName, expectedFieldName);
}
}
/**
* Moves the JSON parser ahead by one token
*
* @return The current JSON token
* @throws IOException
*/
public JsonToken nextToken() throws IOException {
return jsonParser.nextToken();
}
/**
* If the current token is a field name, this method returns the name of
* the field.
*
* @return A String object containing the name of the field
* @throws IOException
*/
public String getFieldName() throws IOException {
if (jsonParser.getCurrentToken() != JsonToken.FIELD_NAME) {
throw new IOException("Expected a field of type " + JsonToken.FIELD_NAME +
", but found a field of type " +
jsonParser.getCurrentToken());
}
return jsonParser.getCurrentName();
}
/**
* The JsonParser class exposes a number of methods for reading values, which
* are named confusingly. They also don't move the current token ahead, which
* makes us use statements like jsonParser.nextToken() everywhere in the code.
*
* This wrapper method abstracts all that.
*
* @param valueType The type of the value to be read
* @param <T>
* @return
* @throws IOException
*/
public <T> T readValueAs(Class<T> valueType)
throws IOException {
jsonParser.nextToken();
if (valueType == String.class) {
return valueType.cast(jsonParser.getText());
}
return jsonParser.readValueAs(valueType);
}
}