5
0
mirror of https://github.com/apache/sqoop.git synced 2025-05-10 05:09:41 +08:00

SQOOP-812: Serialization of Configuration objects to and from json is not working properly

(Jarek Jarcec Cecho via Cheolsoo Park)
This commit is contained in:
Cheolsoo Park 2013-01-22 16:00:27 -08:00
parent 52dece8fa8
commit 612060139a
2 changed files with 178 additions and 68 deletions

View File

@ -32,9 +32,7 @@
/** /**
* Util class for transforming data from correctly annotated configuration * Util class for transforming data from correctly annotated configuration
* objects to form structures and vice-versa. * objects to different structures and vice-versa.
*
* TODO(jarcec): JSON methods needs rewrittion!
*/ */
public class FormUtils { public class FormUtils {
@ -271,6 +269,13 @@ public static void applyValidation(List<MForm> forms, Validation validation) {
} }
} }
/**
* Convert configuration object to JSON. Only filled properties are serialized,
* properties with null value are skipped.
*
* @param configuration Correctly annotated configuration object
* @return String of JSON representation
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static String toJson(Object configuration) { public static String toJson(Object configuration) {
Class klass = configuration.getClass(); Class klass = configuration.getClass();
@ -284,100 +289,161 @@ public static String toJson(Object configuration) {
"Missing annotation Configuration on class " + klass.getName()); "Missing annotation Configuration on class " + klass.getName());
} }
JSONObject jsonObject = new JSONObject(); JSONObject jsonOutput = new JSONObject();
// Iterate over all declared fields // Iterate over all declared fields
for (Field field : klass.getDeclaredFields()) { for (Field formField : klass.getDeclaredFields()) {
field.setAccessible(true); formField.setAccessible(true);
String fieldName = field.getName(); String formName = formField.getName();
// Each field that should be part of user input should have Input // We're processing only form validations
// annotation. Form formAnnotation = formField.getAnnotation(Form.class);
Input inputAnnotation = field.getAnnotation(Input.class); if(formAnnotation == null) {
continue;
}
Object value; Object formValue;
try { try {
value = field.get(configuration); formValue = formField.get(configuration);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new SqoopException(ModelError.MODEL_005, throw new SqoopException(ModelError.MODEL_005,
"Issue with field " + field.getName(), e); "Issue with field " + formName, e);
} }
// Do not serialize all values JSONObject jsonForm = new JSONObject();
if(inputAnnotation != null && value != null) {
Class type = field.getType();
// We need to support NULL, so we do not support primitive types // Now process each input on the form
if(type.isPrimitive()) { for(Field inputField : formField.getType().getDeclaredFields()) {
throw new SqoopException(ModelError.MODEL_007, inputField.setAccessible(true);
"Detected primitive type " + type + " for field " + fieldName); String inputName = inputField.getName();
Object value;
try {
value = inputField.get(formValue);
} catch (IllegalAccessException e) {
throw new SqoopException(ModelError.MODEL_005,
"Issue with field " + formName + "." + inputName, e);
} }
if(type == String.class) { Input inputAnnotation = inputField.getAnnotation(Input.class);
jsonObject.put(fieldName, value);
} else if (type.isAssignableFrom(Map.class)) { // Do not serialize all values
JSONObject map = new JSONObject(); if(inputAnnotation != null && value != null) {
for(Object key : ((Map)value).keySet()) { Class type = inputField.getType();
map.put(key, map.get(key));
// We need to support NULL, so we do not support primitive types
if(type.isPrimitive()) {
throw new SqoopException(ModelError.MODEL_007,
"Detected primitive type " + type + " for field " + formName + "." + inputName);
}
if(type == String.class) {
jsonForm.put(inputName, value);
} else if (type.isAssignableFrom(Map.class)) {
JSONObject map = new JSONObject();
for(Object key : ((Map)value).keySet()) {
map.put(key, ((Map)value).get(key));
}
jsonForm.put(inputName, map);
} else if(type == Integer.class) {
jsonForm.put(inputName, value);
} else if(type.isEnum()) {
jsonForm.put(inputName, value.toString());
} else {
throw new SqoopException(ModelError.MODEL_004,
"Unsupported type " + type.getName() + " for input " + formName + "." + inputName);
} }
jsonObject.put(fieldName, map);
} else if(type == Integer.class) {
jsonObject.put(fieldName, value);
} else if(type.isEnum()) {
jsonObject.put(fieldName, value);
} else {
throw new SqoopException(ModelError.MODEL_004,
"Unsupported type " + type.getName() + " for input " + fieldName);
} }
} }
jsonOutput.put(formName, jsonForm);
} }
return jsonObject.toJSONString(); return jsonOutput.toJSONString();
} }
// TODO(jarcec): This method currently do not iterate over all fields and /**
// therefore some fields might have original values when original object will * Parse given input JSON string and move it's values to given configuration
// be reused. This is unfortunately not acceptable. * object.
*
* @param json JSON representation of the configuration object
* @param configuration Configuration object to be filled
*/
public static void fillValues(String json, Object configuration) { public static void fillValues(String json, Object configuration) {
Class klass = configuration.getClass(); Class klass = configuration.getClass();
JSONObject jsonObject = (JSONObject) JSONValue.parse(json); JSONObject jsonForms = (JSONObject) JSONValue.parse(json);
for(Object k : jsonObject.keySet()) { for(Field formField : klass.getDeclaredFields()) {
String key = (String)k; formField.setAccessible(true);
String formName = formField.getName();
Field field; // We're processing only form validations
try { Form formAnnotation = formField.getAnnotation(Form.class);
field = klass.getDeclaredField(key); if(formAnnotation == null) {
} catch (NoSuchFieldException e) { continue;
throw new SqoopException(ModelError.MODEL_006,
"Missing field " + key, e);
} }
// We need to access this field even if it would be declared as private
field.setAccessible(true);
Class type = field.getType();
try { try {
if(type == String.class) { formField.set(configuration, formField.getType().newInstance());
field.set(configuration, jsonObject.get(key)); } catch (Exception e) {
} else if (type.isAssignableFrom(Map.class)) { throw new SqoopException(ModelError.MODEL_005,
Map<String, String> map = new HashMap<String, String>(); "Issue with field " + formName, e);
for(Object kk : jsonObject.keySet()) { }
map.put((String)kk, (String)jsonObject.get(kk));
} JSONObject jsonInputs = (JSONObject) jsonForms.get(formField.getName());
field.set(key, map); if(jsonInputs == null) {
} else if(type == Integer.class) { continue;
field.set(configuration, jsonObject.get(key)); }
} else if(type == Integer.class) {
field.set(configuration, Enum.valueOf((Class<? extends Enum>)field.getType(), (String) jsonObject.get(key))); Object formValue;
} else { try {
throw new SqoopException(ModelError.MODEL_004, formValue = formField.get(configuration);
"Unsupported type " + type.getName() + " for input " + key);
}
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new SqoopException(ModelError.MODEL_005, throw new SqoopException(ModelError.MODEL_005,
"Issue with field " + field.getName(), e); "Issue with field " + formName, e);
}
for(Field inputField : formField.getType().getDeclaredFields()) {
inputField.setAccessible(true);
String inputName = inputField.getName();
Input inputAnnotation = inputField.getAnnotation(Input.class);
if(inputAnnotation == null || jsonInputs.get(inputName) == null) {
try {
inputField.set(formValue, null);
} catch (IllegalAccessException e) {
throw new SqoopException(ModelError.MODEL_005,
"Issue with field " + formName + "." + inputName, e);
}
continue;
}
Class type = inputField.getType();
try {
if(type == String.class) {
inputField.set(formValue, jsonInputs.get(inputName));
} else if (type.isAssignableFrom(Map.class)) {
Map<String, String> map = new HashMap<String, String>();
JSONObject jsonObject = (JSONObject) jsonInputs.get(inputName);
for(Object key : jsonObject.keySet()) {
map.put((String)key, (String)jsonObject.get(key));
}
inputField.set(formValue, map);
} else if(type == Integer.class) {
inputField.set(formValue, ((Long)jsonInputs.get(inputName)).intValue());
} else if(type.isEnum()) {
inputField.set(formValue, Enum.valueOf((Class<? extends Enum>) inputField.getType(), (String) jsonInputs.get(inputName)));
} else {
throw new SqoopException(ModelError.MODEL_004,
"Unsupported type " + type.getName() + " for input " + formName + "." + inputName);
}
} catch (IllegalAccessException e) {
throw new SqoopException(ModelError.MODEL_005,
"Issue with field " + formName + "." + inputName, e);
}
} }
} }
} }

View File

@ -114,6 +114,39 @@ public void testApplyValidation() {
forms.get(0).getInputs().get(1).getValidationMessage()); forms.get(0).getInputs().get(1).getValidationMessage());
} }
public void testJson() {
Config config = new Config();
config.aForm.a1 = "A";
config.bForm.b2 = "B";
config.cForm.intValue = 4;
config.cForm.map.put("C", "D");
config.cForm.enumeration = Enumeration.X;
String json = FormUtils.toJson(config);
Config targetConfig = new Config();
// Old values from should be always removed
targetConfig.aForm.a2 = "X";
targetConfig.bForm.b1 = "Y";
// Nulls in forms shouldn't be an issue either
targetConfig.cForm = null;
FormUtils.fillValues(json, targetConfig);
assertEquals("A", targetConfig.aForm.a1);
assertNull(targetConfig.aForm.a2);
assertNull(targetConfig.bForm.b1);
assertEquals("B", targetConfig.bForm.b2);
assertEquals((Integer)4, targetConfig.cForm.intValue);
assertEquals(1, targetConfig.cForm.map.size());
assertTrue(targetConfig.cForm.map.containsKey("C"));
assertEquals("D", targetConfig.cForm.map.get("C"));
assertEquals(Enumeration.X, targetConfig.cForm.enumeration);
}
protected Validation getValidation() { protected Validation getValidation() {
Map<Validation.FormInput, Validation.Message> messages Map<Validation.FormInput, Validation.Message> messages
= new HashMap<Validation.FormInput, Validation.Message>(); = new HashMap<Validation.FormInput, Validation.Message>();
@ -153,6 +186,7 @@ protected List<MForm> getForms() {
inputs = new LinkedList<MInput<?>>(); inputs = new LinkedList<MInput<?>>();
inputs.add(new MIntegerInput("cForm.intValue")); inputs.add(new MIntegerInput("cForm.intValue"));
inputs.add(new MMapInput("cForm.map")); inputs.add(new MMapInput("cForm.map"));
inputs.add(new MEnumInput("cForm.enumeration", new String[]{"X", "Y"}));
ret.add(new MForm("cForm", inputs)); ret.add(new MForm("cForm", inputs));
return ret; return ret;
@ -193,6 +227,11 @@ public static class BForm {
public static class CForm { public static class CForm {
@Input Integer intValue; @Input Integer intValue;
@Input Map<String, String> map; @Input Map<String, String> map;
@Input Enumeration enumeration;
public CForm() {
map = new HashMap<String, String>();
}
} }
@FormClass @FormClass
@ -202,4 +241,9 @@ public static class DForm {
public static class ConfigWithout { public static class ConfigWithout {
} }
enum Enumeration {
X,
Y,
}
} }