HADOOP-13628. Support to retrieve specific property from configuration via REST API. Contributed by Weiwei Yang

(cherry picked from commit 00160f71b6)
This commit is contained in:
John Zhuge 2017-08-11 11:02:14 -07:00
parent 8fdcf86e64
commit 407bbe39fa
4 changed files with 491 additions and 73 deletions

View File

@ -76,11 +76,14 @@ public class ConfServlet extends HttpServlet {
response.setContentType("application/json; charset=utf-8"); response.setContentType("application/json; charset=utf-8");
} }
String name = request.getParameter("name");
Writer out = response.getWriter(); Writer out = response.getWriter();
try { try {
writeResponse(getConfFromContext(), out, format); writeResponse(getConfFromContext(), out, format, name);
} catch (BadFormatException bfe) { } catch (BadFormatException bfe) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, bfe.getMessage()); response.sendError(HttpServletResponse.SC_BAD_REQUEST, bfe.getMessage());
} catch (IllegalArgumentException iae) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, iae.getMessage());
} }
out.close(); out.close();
} }
@ -95,17 +98,23 @@ public class ConfServlet extends HttpServlet {
/** /**
* Guts of the servlet - extracted for easy testing. * Guts of the servlet - extracted for easy testing.
*/ */
static void writeResponse(Configuration conf, Writer out, String format) static void writeResponse(Configuration conf,
throws IOException, BadFormatException { Writer out, String format, String propertyName)
throws IOException, IllegalArgumentException, BadFormatException {
if (FORMAT_JSON.equals(format)) { if (FORMAT_JSON.equals(format)) {
Configuration.dumpConfiguration(conf, out); Configuration.dumpConfiguration(conf, propertyName, out);
} else if (FORMAT_XML.equals(format)) { } else if (FORMAT_XML.equals(format)) {
conf.writeXml(out); conf.writeXml(propertyName, out);
} else { } else {
throw new BadFormatException("Bad format: " + format); throw new BadFormatException("Bad format: " + format);
} }
} }
static void writeResponse(Configuration conf, Writer out, String format)
throws IOException, BadFormatException {
writeResponse(conf, out, format, null);
}
public static class BadFormatException extends Exception { public static class BadFormatException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -101,8 +101,9 @@ import org.w3c.dom.Text;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
/** /**
* Provides access to configuration parameters. * Provides access to configuration parameters.
* *
* <h4 id="Resources">Resources</h4> * <h4 id="Resources">Resources</h4>
@ -2756,14 +2757,37 @@ public class Configuration implements Iterable<Map.Entry<String,String>>,
writeXml(new OutputStreamWriter(out, "UTF-8")); writeXml(new OutputStreamWriter(out, "UTF-8"));
} }
/** public void writeXml(Writer out) throws IOException {
* Write out the non-default properties in this configuration to the given writeXml(null, out);
* {@link Writer}. }
*
/**
* Write out the non-default properties in this configuration to the
* given {@link Writer}.
*
* <li>
* When property name is not empty and the property exists in the
* configuration, this method writes the property and its attributes
* to the {@link Writer}.
* </li>
* <p>
*
* <li>
* When property name is null or empty, this method writes all the
* configuration properties and their attributes to the {@link Writer}.
* </li>
* <p>
*
* <li>
* When property name is not empty but the property doesn't exist in
* the configuration, this method throws an {@link IllegalArgumentException}.
* </li>
* <p>
* @param out the writer to write to. * @param out the writer to write to.
*/ */
public void writeXml(Writer out) throws IOException { public void writeXml(String propertyName, Writer out)
Document doc = asXmlDocument(); throws IOException, IllegalArgumentException {
Document doc = asXmlDocument(propertyName);
try { try {
DOMSource source = new DOMSource(doc); DOMSource source = new DOMSource(doc);
@ -2783,62 +2807,180 @@ public class Configuration implements Iterable<Map.Entry<String,String>>,
/** /**
* Return the XML DOM corresponding to this Configuration. * Return the XML DOM corresponding to this Configuration.
*/ */
private synchronized Document asXmlDocument() throws IOException { private synchronized Document asXmlDocument(String propertyName)
throws IOException, IllegalArgumentException {
Document doc; Document doc;
try { try {
doc = doc = DocumentBuilderFactory
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); .newInstance()
.newDocumentBuilder()
.newDocument();
} catch (ParserConfigurationException pe) { } catch (ParserConfigurationException pe) {
throw new IOException(pe); throw new IOException(pe);
} }
Element conf = doc.createElement("configuration"); Element conf = doc.createElement("configuration");
doc.appendChild(conf); doc.appendChild(conf);
conf.appendChild(doc.createTextNode("\n")); conf.appendChild(doc.createTextNode("\n"));
handleDeprecation(); //ensure properties is set and deprecation is handled handleDeprecation(); //ensure properties is set and deprecation is handled
for (Enumeration<Object> e = properties.keys(); e.hasMoreElements();) {
String name = (String)e.nextElement(); if(!Strings.isNullOrEmpty(propertyName)) {
Object object = properties.get(name); if (!properties.containsKey(propertyName)) {
String value = null; // given property not found, illegal argument
if (object instanceof String) { throw new IllegalArgumentException("Property " +
value = (String) object; propertyName + " not found");
}else { } else {
continue; // given property is found, write single property
appendXMLProperty(doc, conf, propertyName);
conf.appendChild(doc.createTextNode("\n"));
} }
Element propNode = doc.createElement("property"); } else {
conf.appendChild(propNode); // append all elements
for (Enumeration<Object> e = properties.keys(); e.hasMoreElements();) {
Element nameNode = doc.createElement("name"); appendXMLProperty(doc, conf, (String)e.nextElement());
nameNode.appendChild(doc.createTextNode(name)); conf.appendChild(doc.createTextNode("\n"));
propNode.appendChild(nameNode);
Element valueNode = doc.createElement("value");
valueNode.appendChild(doc.createTextNode(value));
propNode.appendChild(valueNode);
if (updatingResource != null) {
String[] sources = updatingResource.get(name);
if(sources != null) {
for(String s : sources) {
Element sourceNode = doc.createElement("source");
sourceNode.appendChild(doc.createTextNode(s));
propNode.appendChild(sourceNode);
}
}
} }
conf.appendChild(doc.createTextNode("\n"));
} }
return doc; return doc;
} }
/** /**
* Writes out all the parameters and their properties (final and resource) to * Append a property with its attributes to a given {#link Document}
* the given {@link Writer} * if the property is found in configuration.
* The format of the output would be *
* { "properties" : [ {key1,value1,key1.isFinal,key1.resource}, {key2,value2, * @param doc
* key2.isFinal,key2.resource}... ] } * @param conf
* It does not output the parameters of the configuration object which is * @param propertyName
* loaded from an input stream. */
private synchronized void appendXMLProperty(Document doc, Element conf,
String propertyName) {
// skip writing if given property name is empty or null
if (!Strings.isNullOrEmpty(propertyName)) {
String value = properties.getProperty(propertyName);
if (value != null) {
Element propNode = doc.createElement("property");
conf.appendChild(propNode);
Element nameNode = doc.createElement("name");
nameNode.appendChild(doc.createTextNode(propertyName));
propNode.appendChild(nameNode);
Element valueNode = doc.createElement("value");
valueNode.appendChild(doc.createTextNode(
properties.getProperty(propertyName)));
propNode.appendChild(valueNode);
Element finalNode = doc.createElement("final");
finalNode.appendChild(doc.createTextNode(
String.valueOf(finalParameters.contains(propertyName))));
propNode.appendChild(finalNode);
if (updatingResource != null) {
String[] sources = updatingResource.get(propertyName);
if(sources != null) {
for(String s : sources) {
Element sourceNode = doc.createElement("source");
sourceNode.appendChild(doc.createTextNode(s));
propNode.appendChild(sourceNode);
}
}
}
}
}
}
/**
* Writes properties and their attributes (final and resource)
* to the given {@link Writer}.
*
* <li>
* When propertyName is not empty, and the property exists
* in the configuration, the format of the output would be,
* <pre>
* {
* "property": {
* "key" : "key1",
* "value" : "value1",
* "isFinal" : "key1.isFinal",
* "resource" : "key1.resource"
* }
* }
* </pre>
* </li>
*
* <li>
* When propertyName is null or empty, it behaves same as
* {@link #dumpConfiguration(Configuration, Writer)}, the
* output would be,
* <pre>
* { "properties" :
* [ { key : "key1",
* value : "value1",
* isFinal : "key1.isFinal",
* resource : "key1.resource" },
* { key : "key2",
* value : "value2",
* isFinal : "ke2.isFinal",
* resource : "key2.resource" }
* ]
* }
* </pre>
* </li>
*
* <li>
* When propertyName is not empty, and the property is not
* found in the configuration, this method will throw an
* {@link IllegalArgumentException}.
* </li>
* <p>
* @param config the configuration
* @param propertyName property name
* @param out the Writer to write to
* @throws IOException
* @throws IllegalArgumentException when property name is not
* empty and the property is not found in configuration
**/
public static void dumpConfiguration(Configuration config,
String propertyName, Writer out) throws IOException {
if(Strings.isNullOrEmpty(propertyName)) {
dumpConfiguration(config, out);
} else if (Strings.isNullOrEmpty(config.get(propertyName))) {
throw new IllegalArgumentException("Property " +
propertyName + " not found");
} else {
JsonFactory dumpFactory = new JsonFactory();
JsonGenerator dumpGenerator = dumpFactory.createJsonGenerator(out);
dumpGenerator.writeStartObject();
dumpGenerator.writeFieldName("property");
appendJSONProperty(dumpGenerator, config, propertyName);
dumpGenerator.writeEndObject();
dumpGenerator.flush();
}
}
/**
* Writes out all properties and their attributes (final and resource) to
* the given {@link Writer}, the format of the output would be,
*
* <pre>
* { "properties" :
* [ { key : "key1",
* value : "value1",
* isFinal : "key1.isFinal",
* resource : "key1.resource" },
* { key : "key2",
* value : "value2",
* isFinal : "ke2.isFinal",
* resource : "key2.resource" }
* ]
* }
* </pre>
*
* It does not output the properties of the configuration object which
* is loaded from an input stream.
* <p>
*
* @param config the configuration
* @param out the Writer to write to * @param out the Writer to write to
* @throws IOException * @throws IOException
*/ */
@ -2852,29 +2994,47 @@ public class Configuration implements Iterable<Map.Entry<String,String>>,
dumpGenerator.flush(); dumpGenerator.flush();
synchronized (config) { synchronized (config) {
for (Map.Entry<Object,Object> item: config.getProps().entrySet()) { for (Map.Entry<Object,Object> item: config.getProps().entrySet()) {
dumpGenerator.writeStartObject(); appendJSONProperty(dumpGenerator,
dumpGenerator.writeStringField("key", (String) item.getKey()); config,
dumpGenerator.writeStringField("value", item.getKey().toString());
config.get((String) item.getKey()));
dumpGenerator.writeBooleanField("isFinal",
config.finalParameters.contains(item.getKey()));
String[] resources = config.updatingResource.get(item.getKey());
String resource = UNKNOWN_RESOURCE;
if(resources != null && resources.length > 0) {
resource = resources[0];
}
dumpGenerator.writeStringField("resource", resource);
dumpGenerator.writeEndObject();
} }
} }
dumpGenerator.writeEndArray(); dumpGenerator.writeEndArray();
dumpGenerator.writeEndObject(); dumpGenerator.writeEndObject();
dumpGenerator.flush(); dumpGenerator.flush();
} }
/**
* Write property and its attributes as json format to given
* {@link JsonGenerator}.
*
* @param jsonGen json writer
* @param config configuration
* @param name property name
* @throws IOException
*/
private static void appendJSONProperty(JsonGenerator jsonGen,
Configuration config, String name) throws IOException {
// skip writing if given property name is empty or null
if(!Strings.isNullOrEmpty(name) && jsonGen != null) {
jsonGen.writeStartObject();
jsonGen.writeStringField("key", name);
jsonGen.writeStringField("value", config.get(name));
jsonGen.writeBooleanField("isFinal",
config.finalParameters.contains(name));
String[] resources = config.updatingResource.get(name);
String resource = UNKNOWN_RESOURCE;
if(resources != null && resources.length > 0) {
resource = resources[0];
}
jsonGen.writeStringField("resource", resource);
jsonGen.writeEndObject();
}
}
/** /**
* Get the {@link ClassLoader} for this job. * Get the {@link ClassLoader} for this job.
* *
* @return the correct class loader. * @return the correct class loader.
*/ */
public ClassLoader getClassLoader() { public ClassLoader getClassLoader() {

View File

@ -18,11 +18,15 @@
package org.apache.hadoop.conf; package org.apache.hadoop.conf;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.PrintWriter;
import java.io.StringReader; import java.io.StringReader;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
@ -34,17 +38,36 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import junit.framework.TestCase; import com.google.common.base.Strings;
import org.apache.hadoop.http.HttpServer2;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;
import static org.junit.Assert.*;
/** /**
* Basic test case that the ConfServlet can write configuration * Basic test case that the ConfServlet can write configuration
* to its output in XML and JSON format. * to its output in XML and JSON format.
*/ */
public class TestConfServlet extends TestCase { public class TestConfServlet {
private static final String TEST_KEY = "testconfservlet.key"; private static final String TEST_KEY = "testconfservlet.key";
private static final String TEST_VAL = "testval"; private static final String TEST_VAL = "testval";
private static final Map<String, String> TEST_PROPERTIES =
new HashMap<String, String>();
private static final Map<String, String> TEST_FORMATS =
new HashMap<String, String>();
@BeforeClass
public static void initTestProperties() {
TEST_PROPERTIES.put("test.key1", "value1");
TEST_PROPERTIES.put("test.key2", "value2");
TEST_PROPERTIES.put("test.key3", "value3");
TEST_FORMATS.put(ConfServlet.FORMAT_XML, "application/xml");
TEST_FORMATS.put(ConfServlet.FORMAT_JSON, "application/json");
}
private Configuration getTestConf() { private Configuration getTestConf() {
Configuration testConf = new Configuration(); Configuration testConf = new Configuration();
@ -52,6 +75,14 @@ public class TestConfServlet extends TestCase {
return testConf; return testConf;
} }
private Configuration getMultiPropertiesConf() {
Configuration testConf = new Configuration(false);
for(String key : TEST_PROPERTIES.keySet()) {
testConf.set(key, TEST_PROPERTIES.get(key));
}
return testConf;
}
@Test @Test
public void testParseHeaders() throws Exception { public void testParseHeaders() throws Exception {
HashMap<String, String> verifyMap = new HashMap<String, String>(); HashMap<String, String> verifyMap = new HashMap<String, String>();
@ -71,6 +102,92 @@ public class TestConfServlet extends TestCase {
} }
} }
private void verifyGetProperty(Configuration conf, String format,
String propertyName) throws Exception {
StringWriter sw = null;
PrintWriter pw = null;
ConfServlet service = null;
try {
service = new ConfServlet();
ServletConfig servletConf = mock(ServletConfig.class);
ServletContext context = mock(ServletContext.class);
service.init(servletConf);
when(context.getAttribute(HttpServer2.CONF_CONTEXT_ATTRIBUTE))
.thenReturn(conf);
when(service.getServletContext())
.thenReturn(context);
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(HttpHeaders.ACCEPT))
.thenReturn(TEST_FORMATS.get(format));
when(request.getParameter("name"))
.thenReturn(propertyName);
HttpServletResponse response = mock(HttpServletResponse.class);
sw = new StringWriter();
pw = new PrintWriter(sw);
when(response.getWriter()).thenReturn(pw);
// response request
service.doGet(request, response);
String result = sw.toString().trim();
// if property name is null or empty, expect all properties
// in the response
if (Strings.isNullOrEmpty(propertyName)) {
for(String key : TEST_PROPERTIES.keySet()) {
assertTrue(result.contains(key) &&
result.contains(TEST_PROPERTIES.get(key)));
}
} else {
if(conf.get(propertyName) != null) {
// if property name is not empty and property is found
assertTrue(result.contains(propertyName));
for(String key : TEST_PROPERTIES.keySet()) {
if(!key.equals(propertyName)) {
assertFalse(result.contains(key));
}
}
} else {
// if property name is not empty, and it's not in configuration
// expect proper error code and error message is set to the response
Mockito.verify(response).sendError(
Mockito.eq(HttpServletResponse.SC_NOT_FOUND),
Mockito.eq("Property " + propertyName + " not found"));
}
}
} finally {
if (sw != null) {
sw.close();
}
if (pw != null) {
pw.close();
}
if (service != null) {
service.destroy();
}
}
}
@Test
public void testGetProperty() throws Exception {
Configuration configurations = getMultiPropertiesConf();
// list various of property names
String[] testKeys = new String[] {
"test.key1",
"test.unknown.key",
"",
"test.key2",
null
};
for(String format : TEST_FORMATS.keySet()) {
for(String key : testKeys) {
verifyGetProperty(configurations, format, key);
}
}
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testWriteJson() throws Exception { public void testWriteJson() throws Exception {
@ -109,7 +226,6 @@ public class TestConfServlet extends TestCase {
for (int i = 0; i < nameNodes.getLength(); i++) { for (int i = 0; i < nameNodes.getLength(); i++) {
Node nameNode = nameNodes.item(i); Node nameNode = nameNodes.item(i);
String key = nameNode.getTextContent(); String key = nameNode.getTextContent();
System.err.println("xml key: " + key);
if (TEST_KEY.equals(key)) { if (TEST_KEY.equals(key)) {
foundSetting = true; foundSetting = true;
Element propertyElem = (Element)nameNode.getParentNode(); Element propertyElem = (Element)nameNode.getParentNode();

View File

@ -1022,7 +1022,19 @@ public class TestConfiguration extends TestCase {
this.properties = properties; this.properties = properties;
} }
} }
static class SingleJsonConfiguration {
private JsonProperty property;
public JsonProperty getProperty() {
return property;
}
public void setProperty(JsonProperty property) {
this.property = property;
}
}
static class JsonProperty { static class JsonProperty {
String key; String key;
public String getKey() { public String getKey() {
@ -1053,7 +1065,14 @@ public class TestConfiguration extends TestCase {
boolean isFinal; boolean isFinal;
String resource; String resource;
} }
private Configuration getActualConf(String xmlStr) {
Configuration ac = new Configuration(false);
InputStream in = new ByteArrayInputStream(xmlStr.getBytes());
ac.addResource(in);
return ac;
}
public void testGetSetTrimmedNames() throws IOException { public void testGetSetTrimmedNames() throws IOException {
Configuration conf = new Configuration(false); Configuration conf = new Configuration(false);
conf.set(" name", "value"); conf.set(" name", "value");
@ -1062,7 +1081,121 @@ public class TestConfiguration extends TestCase {
assertEquals("value", conf.getRaw(" name ")); assertEquals("value", conf.getRaw(" name "));
} }
public void testDumpConfiguration () throws IOException { public void testDumpProperty() throws IOException {
StringWriter outWriter = new StringWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonStr = null;
String xmlStr = null;
try {
Configuration testConf = new Configuration(false);
out = new BufferedWriter(new FileWriter(CONFIG));
startConfig();
appendProperty("test.key1", "value1");
appendProperty("test.key2", "value2", true);
appendProperty("test.key3", "value3");
endConfig();
Path fileResource = new Path(CONFIG);
testConf.addResource(fileResource);
out.close();
// case 1: dump an existing property
// test json format
outWriter = new StringWriter();
Configuration.dumpConfiguration(testConf, "test.key2", outWriter);
jsonStr = outWriter.toString();
outWriter.close();
mapper = new ObjectMapper();
SingleJsonConfiguration jconf1 =
mapper.readValue(jsonStr, SingleJsonConfiguration.class);
JsonProperty jp1 = jconf1.getProperty();
assertEquals("test.key2", jp1.getKey());
assertEquals("value2", jp1.getValue());
assertEquals(true, jp1.isFinal);
assertEquals(fileResource.toUri().getPath(), jp1.getResource());
// test xml format
outWriter = new StringWriter();
testConf.writeXml("test.key2", outWriter);
xmlStr = outWriter.toString();
outWriter.close();
Configuration actualConf1 = getActualConf(xmlStr);
assertEquals(1, actualConf1.size());
assertEquals("value2", actualConf1.get("test.key2"));
assertTrue(actualConf1.getFinalParameters().contains("test.key2"));
assertEquals(fileResource.toUri().getPath(),
actualConf1.getPropertySources("test.key2")[0]);
// case 2: dump an non existing property
// test json format
try {
outWriter = new StringWriter();
Configuration.dumpConfiguration(testConf,
"test.unknown.key", outWriter);
outWriter.close();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
assertTrue(e.getMessage().contains("test.unknown.key") &&
e.getMessage().contains("not found"));
}
// test xml format
try {
outWriter = new StringWriter();
testConf.writeXml("test.unknown.key", outWriter);
outWriter.close();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
assertTrue(e.getMessage().contains("test.unknown.key") &&
e.getMessage().contains("not found"));
}
// case 3: specify a null property, ensure all configurations are dumped
outWriter = new StringWriter();
Configuration.dumpConfiguration(testConf, null, outWriter);
jsonStr = outWriter.toString();
mapper = new ObjectMapper();
JsonConfiguration jconf3 =
mapper.readValue(jsonStr, JsonConfiguration.class);
assertEquals(3, jconf3.getProperties().length);
outWriter = new StringWriter();
testConf.writeXml(null, outWriter);
xmlStr = outWriter.toString();
outWriter.close();
Configuration actualConf3 = getActualConf(xmlStr);
assertEquals(3, actualConf3.size());
assertTrue(actualConf3.getProps().containsKey("test.key1") &&
actualConf3.getProps().containsKey("test.key2") &&
actualConf3.getProps().containsKey("test.key3"));
// case 4: specify an empty property, ensure all configurations are dumped
outWriter = new StringWriter();
Configuration.dumpConfiguration(testConf, "", outWriter);
jsonStr = outWriter.toString();
mapper = new ObjectMapper();
JsonConfiguration jconf4 =
mapper.readValue(jsonStr, JsonConfiguration.class);
assertEquals(3, jconf4.getProperties().length);
outWriter = new StringWriter();
testConf.writeXml("", outWriter);
xmlStr = outWriter.toString();
outWriter.close();
Configuration actualConf4 = getActualConf(xmlStr);
assertEquals(3, actualConf4.size());
assertTrue(actualConf4.getProps().containsKey("test.key1") &&
actualConf4.getProps().containsKey("test.key2") &&
actualConf4.getProps().containsKey("test.key3"));
} finally {
if(outWriter != null) {
outWriter.close();
}
if(out != null) {
out.close();
}
}
}
public void testDumpConfiguration() throws IOException {
StringWriter outWriter = new StringWriter(); StringWriter outWriter = new StringWriter();
Configuration.dumpConfiguration(conf, outWriter); Configuration.dumpConfiguration(conf, outWriter);
String jsonStr = outWriter.toString(); String jsonStr = outWriter.toString();