fix bug serialize response containing special cases

This commit is contained in:
Mengyi Zhou 2016-02-22 14:03:10 +08:00
parent 586f3b29ec
commit f427f64e63
9 changed files with 238 additions and 92 deletions

View file

@ -4,6 +4,7 @@ package com.ctrip.zeus.client;
import com.ctrip.zeus.model.entity.Group;
import com.ctrip.zeus.model.entity.GroupList;
import com.ctrip.zeus.model.transform.DefaultJsonParser;
import com.ctrip.zeus.support.GenericSerializer;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
@ -42,18 +43,14 @@ public class GroupClient extends AbstractRestClient {
public Response add(Group group) {
return getTarget().path("/api/group/new").request().headers(getDefaultHeaders())
.post(Entity.entity(
String.format(Group.JSON, group),
MediaType.APPLICATION_JSON
));
GenericSerializer.writeJson(group), MediaType.APPLICATION_JSON));
}
public Response update(Group group) {
return getTarget().path("/api/group/update").request().headers(getDefaultHeaders())
.post(Entity.entity(
String.format(Group.JSON, group),
MediaType.APPLICATION_JSON
));
GenericSerializer.writeJson(group), MediaType.APPLICATION_JSON));
}

View file

@ -4,6 +4,7 @@ package com.ctrip.zeus.client;
import com.ctrip.zeus.model.entity.Slb;
import com.ctrip.zeus.model.entity.SlbList;
import com.ctrip.zeus.model.transform.DefaultJsonParser;
import com.ctrip.zeus.support.GenericSerializer;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
@ -42,17 +43,13 @@ public class SlbClient extends AbstractRestClient {
public Response add(Slb slb) {
return getTarget().path("/api/slb/new").request().headers(getDefaultHeaders())
.post(Entity.entity(
String.format(Slb.JSON, slb),
MediaType.APPLICATION_JSON
));
GenericSerializer.writeJson(slb), MediaType.APPLICATION_JSON));
}
public Response update(Slb slb) {
return getTarget().path("/api/slb/update").request().headers(getDefaultHeaders())
.post(Entity.entity(
String.format(Slb.JSON, slb),
MediaType.APPLICATION_JSON
));
GenericSerializer.writeJson(slb), MediaType.APPLICATION_JSON));
}
public Response delete(Long slbId) {

View file

@ -5,6 +5,8 @@ import com.ctrip.zeus.restful.message.Message;
import com.ctrip.zeus.restful.message.ResponseHandler;
import com.ctrip.zeus.restful.response.entity.SuccessMessage;
import com.ctrip.zeus.support.GenericSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.ws.rs.core.MediaType;
@ -18,30 +20,39 @@ import java.util.Set;
*/
@Component("responseHandler")
public class DefaultResponseHandler implements ResponseHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Set<MediaType> acceptedMediaTypes = getDefault();
private static final MediaType defaultMediaType = MediaType.APPLICATION_JSON_TYPE;
public Message generateMessage(Object object, String type) throws Exception {
public Message generateMessage(Object object, MediaType type) throws Exception {
ZeusResponse zr = new ZeusResponse();
if (object == null)
return zr;
if (object instanceof String) {
object = new SuccessMessage().setMessage((String) object);
}
if (type.equals(MediaType.APPLICATION_XML)) {
zr.setResponse(GenericSerializer.writeXml(object));
} else {
try {
zr.setResponse(GenericSerializer.writeJson(object));
} catch (Exception ex) {
if (object instanceof Serializable) {
zr.setResponse((Serializable) object);
} else {
throw new ValidationException("Response object cannot be serialized.");
}
try {
if (type.equals(MediaType.APPLICATION_XML_TYPE)) {
zr.setResponse(GenericSerializer.writeXml(object).replace("%", "%%"));
return zr;
}
if (type.equals(MediaType.APPLICATION_JSON_TYPE)) {
zr.setResponse(GenericSerializer.writeJson(object).replace("%", "%%"));
return zr;
}
throw new ValidationException("Unaccepted media type " + type.getType() + ".");
} catch (Exception ex) {
if (object instanceof Serializable) {
zr.setResponse((Serializable) object);
return zr;
} else {
String error = "Response object cannot be serialized.";
logger.error(error, ex);
throw new ValidationException(error);
}
}
return zr;
}
@Override
@ -49,16 +60,18 @@ public class DefaultResponseHandler implements ResponseHandler {
if (object == null)
return Response.status(Response.Status.OK).type(mediaType).build();
if (mediaType != null && acceptedMediaTypes.contains(mediaType)) {
Message response = generateMessage(object, mediaType.toString());
Message response = generateMessage(object, mediaType);
return Response.status(response.getStatus()).entity(response.getResponse())
.type(mediaType).build();
}
try {
Message response = generateMessage(object, defaultMediaType.toString());
Message response = generateMessage(object, defaultMediaType);
return Response.status(response.getStatus()).entity(response.getResponse())
.type(defaultMediaType).build();
} catch (Exception ex) {
throw new ValidationException("Unaccepted media type.");
String error = "Response cannot be serialized using application/json by default.";
logger.error(error, ex);
throw new ValidationException(error);
}
}

View file

@ -18,14 +18,14 @@ import javax.ws.rs.core.Response;
public class ErrorResponseHandler implements ResponseHandler {
private static final MediaType defaultMediaType = MediaType.APPLICATION_JSON_TYPE;
public Message generateMessage(Throwable object, String type, boolean printStackTrace) throws Exception {
public Message generateMessage(Throwable object, MediaType type, boolean printStackTrace) throws Exception {
ErrorMessage em = ExceptionUtils.getErrorMessage(object, printStackTrace);
ErrorResponse err = new ErrorResponse();
if (type.equals(MediaType.APPLICATION_XML)) {
err.setResponse(GenericSerializer.writeXml(em));
err.setResponse(GenericSerializer.writeXml(em).replace("%", "%%"));
} else {
err.setResponse(GenericSerializer.writeJson(em));
err.setResponse(GenericSerializer.writeJson(em).replace("%", "%%"));
}
if (object instanceof NotFoundException) {
err.setStatus(Response.Status.NOT_FOUND.getStatusCode());
@ -55,7 +55,7 @@ public class ErrorResponseHandler implements ResponseHandler {
if (mediaType == null || !MediaType.APPLICATION_XML_TYPE.equals(mediaType)) {
mediaType = defaultMediaType;
}
Message message = generateMessage((Throwable) object, mediaType.toString(), printStackTrace);
Message message = generateMessage((Throwable) object, mediaType, printStackTrace);
return Response.status(message.getStatus()).entity(message.getResponse())
.type(mediaType).build();
}

View file

@ -191,16 +191,16 @@ public class SlbResource {
throw new Exception("Slb cannot be parsed.");
}
}
s.setName(s.getName().trim());
s.setName(trimIfNotNull(s.getName()));
for (VirtualServer virtualServer : s.getVirtualServers()) {
virtualServer.setName(virtualServer.getName().trim());
virtualServer.setName(trimIfNotNull(virtualServer.getName()));
for (Domain domain : virtualServer.getDomains()) {
domain.setName(domain.getName().trim());
domain.setName(trimIfNotNull(domain.getName()));
}
}
for (SlbServer slbServer : s.getSlbServers()) {
slbServer.setIp(slbServer.getIp().trim());
slbServer.setHostName(slbServer.getHostName().trim());
slbServer.setIp(trimIfNotNull(slbServer.getIp()));
slbServer.setHostName(trimIfNotNull(slbServer.getHostName()));
}
return s;
}
@ -211,14 +211,22 @@ public class SlbResource {
.setName(slb.getName());
}
if ("NORMAL".equalsIgnoreCase(type)) {
return new Slb().setId(slb.getId())
Slb result = new Slb().setId(slb.getId())
.setName(slb.getName())
.setNginxBin(slb.getNginxBin())
.setNginxConf(slb.getNginxConf())
.setNginxWorkerProcesses(slb.getNginxWorkerProcesses())
.setStatus(slb.getStatus())
.setVersion(slb.getVersion());
for (SlbServer slbServer : slb.getSlbServers()) {
result.addSlbServer(slbServer);
}
return result;
}
return slb;
}
private String trimIfNotNull(String value) {
return value != null ? value.trim() : value;
}
}

View file

@ -3,20 +3,21 @@ package com.ctrip.zeus.service.model.handler.impl;
import com.ctrip.zeus.model.entity.Group;
import com.ctrip.zeus.model.entity.Slb;
import com.ctrip.zeus.model.entity.VirtualServer;
import com.ctrip.zeus.support.GenericSerializer;
/**
* Created by zhoumy on 2015/9/22.
*/
public class ContentWriters {
public static String writeVirtualServerContent(VirtualServer vs) {
return String.format(VirtualServer.JSON, vs);
return GenericSerializer.writeJson(vs);
}
public static String writeGroupContent(Group g) {
return String.format(Group.JSON, g);
return GenericSerializer.writeJson(g);
}
public static String writeSlbContent(Slb s) {
return String.format(Slb.JSON, s);
return GenericSerializer.writeJson(s);
}
}

View file

@ -1,23 +1,69 @@
package com.ctrip.zeus.support;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Created by zhoumy on 2015/4/3.
*/
public class GenericSerializer {
private static Logger logger = LoggerFactory.getLogger(GenericSerializer.class);
public static final String JSON = "%#.3s";
public static final String JSON_COMPACT = "%#s";
public static final String XML = "%.3s";
public static final String XML_COMPACT = "%s";
private static Map<String, Class> Builders = new HashMap<>();
private static Map<Class, Method> Methods = new HashMap<>();
private static LoadingCache<Class, Constructor<?>> Ctors = CacheBuilder.newBuilder()
.build(new CacheLoader<Class, Constructor<?>>() {
@Override
public Constructor<?> load(Class aClass) throws Exception {
return aClass.getConstructor(boolean.class);
}
});
public static String writeJson(Object object) {
return writeJson(object, true);
}
public static String writeJson(Object object, boolean pretty) {
if (pretty) {
return String.format(JSON, object);
} else {
return String.format(JSON_COMPACT, object);
String pkg = object.getClass().getPackage().getName();
pkg = pkg.substring(0, pkg.lastIndexOf(".entity"));
Class clazz = Builders.get(pkg + "#Json");
if (clazz == null) {
try {
clazz = Class.forName(pkg + ".transform.DefaultJsonBuilder");
Builders.put(pkg + "#Json", clazz);
} catch (ClassNotFoundException e) {
logger.error("Cannot find " + pkg + ".transform.DefaultJsonBuilder", e);
return "";
}
}
try {
Object builder = Ctors.get(clazz).newInstance(!pretty);
Method m = Methods.get(clazz);
if (m == null) {
try {
m = clazz.getMethod("build", Class.forName(pkg + ".IEntity"));
Methods.put(clazz, m);
} catch (Exception e) {
logger.error("Cannot find method build(IEntity)", e);
}
}
return (String) m.invoke(builder, object);
} catch (Exception e) {
logger.error("Fail to build json data.", e);
return "";
}
}
@ -26,10 +72,33 @@ public class GenericSerializer {
}
public static String writeXml(Object object, boolean pretty) {
if (pretty) {
return String.format(XML, object);
} else {
return String.format(XML_COMPACT, object);
String pkg = object.getClass().getPackage().getName();
pkg = pkg.substring(0, pkg.lastIndexOf(".entity"));
Class clazz = Builders.get(pkg + "#Xml");
if (clazz == null) {
try {
clazz = Class.forName(pkg + ".transform.DefaultXmlBuilder");
Builders.put(pkg + "#Xml", clazz);
} catch (ClassNotFoundException e) {
logger.error("Cannot find " + pkg + ".transform.DefaultXmlBuilder", e);
return "";
}
}
try {
Object builder = Ctors.get(clazz).newInstance(!pretty);
Method m = Methods.get(clazz);
if (m == null) {
try {
m = clazz.getMethod("buildXml", Class.forName(pkg + ".IEntity"));
Methods.put(clazz, m);
} catch (Exception e) {
logger.error("Cannot find method buildXml(IEntity)", e);
}
}
return (String) m.invoke(builder, object);
} catch (Exception e) {
logger.error("Fail to build xml data.", e);
return "";
}
}
}

View file

@ -1,5 +1,6 @@
package com.ctrip.zeus.restful;
import com.ctrip.zeus.AbstractServerTest;
import com.ctrip.zeus.client.GroupClient;
import com.ctrip.zeus.client.SlbClient;
import com.ctrip.zeus.dal.core.*;
@ -7,21 +8,11 @@ import com.ctrip.zeus.exceptions.ValidationException;
import com.ctrip.zeus.model.entity.*;
import com.ctrip.zeus.restful.response.entity.ErrorMessage;
import com.ctrip.zeus.restful.response.transform.DefaultJsonParser;
import com.ctrip.zeus.server.SlbAdminServer;
import com.ctrip.zeus.util.IOUtils;
import com.ctrip.zeus.util.S;
import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.unidal.dal.jdbc.DalException;
import org.unidal.dal.jdbc.datasource.DataSourceManager;
import org.unidal.dal.jdbc.transaction.TransactionManager;
import org.unidal.lookup.ContainerLoader;
import support.AbstractSpringTest;
import support.MysqlDbServer;
import javax.annotation.Resource;
import javax.ws.rs.core.Response;
@ -30,30 +21,12 @@ import java.io.*;
/**
* Created by zhoumy on 2015/4/2.
*/
public class ExceptionTest extends AbstractSpringTest {
private static SlbAdminServer server;
private static MysqlDbServer mysqlDbServer;
public class ExceptionTest extends AbstractServerTest {
@Resource
private GroupDao appDao;
@Resource
private SlbDao slbDao;
@BeforeClass
public static void setUpDb() throws Exception {
S.setPropertyDefaultValue("archaius.deployment.applicationId", "slb-admin");
S.setPropertyDefaultValue("archaius.deployment.environment", "local");
S.setPropertyDefaultValue("server.www.base-dir", new File("").getAbsolutePath() + "/src/main/www");
S.setPropertyDefaultValue("server.temp-dir", new File("").getAbsolutePath() + "/target/temp");
S.setPropertyDefaultValue("CONF_DIR", new File("").getAbsolutePath() + "/conf/test");
mysqlDbServer = new MysqlDbServer();
mysqlDbServer.start();
server = new SlbAdminServer();
server.start();
}
@Test
public void testDalNotFoundException() throws DalException {
Assert.assertNull(appDao.findByName("notExistGroup", GroupEntity.READSET_FULL));
@ -64,7 +37,7 @@ public class ExceptionTest extends AbstractSpringTest {
public void testExceptionInterceptor() throws Exception {
GroupClient ac = new GroupClient("http://127.0.0.1:8099");
Response appResponse = ac.add(new Group());
Assert.assertEquals(500, appResponse.getStatus());
Assert.assertEquals(400, appResponse.getStatus());
String appString = IOUtils.inputStreamStringify((InputStream) appResponse.getEntity());
ErrorMessage aem = DefaultJsonParser.parse(ErrorMessage.class, appString);
@ -73,7 +46,7 @@ public class ExceptionTest extends AbstractSpringTest {
SlbClient sc = new SlbClient("http://127.0.0.1:8099");
Response slbResponse = sc.add(new Slb());
Assert.assertEquals(500, slbResponse.getStatus());
Assert.assertEquals(400, slbResponse.getStatus());
String slbString = IOUtils.inputStreamStringify((InputStream) slbResponse.getEntity());
ErrorMessage sem = DefaultJsonParser.parse(ErrorMessage.class, slbString);
@ -87,15 +60,4 @@ public class ExceptionTest extends AbstractSpringTest {
System.out.println("message:\n" + em.getMessage());
System.out.println("*********************************************");
}
@AfterClass
public static void tearDownDb() throws InterruptedException, ComponentLookupException, ComponentLifecycleException {
server.close();
mysqlDbServer.stop();
DataSourceManager ds = ContainerLoader.getDefaultContainer().lookup(DataSourceManager.class);
ContainerLoader.getDefaultContainer().release(ds);
TransactionManager ts = ContainerLoader.getDefaultContainer().lookup(TransactionManager.class);
ContainerLoader.getDefaultContainer().release(ts);
}
}

View file

@ -0,0 +1,99 @@
package com.ctrip.zeus.restful;
import com.ctrip.zeus.model.entity.*;
import com.ctrip.zeus.restful.message.impl.DefaultResponseHandler;
import com.ctrip.zeus.restful.message.impl.ErrorResponseHandler;
import com.ctrip.zeus.support.GenericSerializer;
import org.junit.Test;
import javax.ws.rs.core.MediaType;
/**
* Created by zhoumy on 2016/2/22.
*/
public class ResponseWritingTest {
@Test
public void testSpecialCases() throws Exception {
System.out.println("************************* Test Special Cases Serialization *************************");
DefaultResponseHandler rh = new DefaultResponseHandler();
System.out.printf(String.valueOf(
rh.generateMessage("%s", MediaType.APPLICATION_JSON_TYPE).getResponse()));
ErrorResponseHandler erh = new ErrorResponseHandler();
System.out.printf(String.valueOf(
erh.generateMessage(new Exception("%s"), MediaType.APPLICATION_JSON_TYPE, false).getResponse()));
}
@Test
public void testFormat() throws Exception {
System.out.println("************************* Test Serialization Format *************************");
DefaultResponseHandler rh = new DefaultResponseHandler();
System.out.printf(String.valueOf(
rh.generateMessage("json", MediaType.APPLICATION_JSON_TYPE).getResponse()));
System.out.printf(String.valueOf(
rh.generateMessage("xml", MediaType.APPLICATION_XML_TYPE).getResponse()));
}
@Test
public void testPerformance() throws Exception {
System.out.println("************************* Test Serialization Performance *************************");
Group group = new Group().setId(9837401263971292L).setName("performace").setAppId("000000").setSsl(false)
.setHealthCheck(new HealthCheck().setIntervals(2000).setFails(1).setPasses(1).setUri("/"))
.setLoadBalancingMethod(new LoadBalancingMethod().setType("roundrobin").setValue("test"));
for (int i = 0; i < 10; i++) {
group.addGroupVirtualServer(new GroupVirtualServer().setPath("/performace").setVirtualServer(new VirtualServer().setId(12345678891028L)));
}
for (int i = 0; i < 10; i++) {
group.addGroupServer(new GroupServer().setPort(80).setWeight(1).setMaxFails(1).setFailTimeout(30).setHostName("0").setIp("10.2.6.201"));
}
GroupList groupList = new GroupList();
for (int i = 0; i < 100; i++) {
groupList.addGroup(group);
}
final String JSON = "%#.3s";
for (int i = 0; i < 150; i++) {
GenericSerializer.writeJson(groupList);
}
for (int i = 0; i < 150; i++) {
String.format(JSON, groupList);
}
final String XML = "%.3s";
for (int i = 0; i < 150; i++) {
GenericSerializer.writeXml(groupList);
}
for (int i = 0; i < 150; i++) {
String.format(XML, groupList);
}
Long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
GenericSerializer.writeJson(groupList);
}
System.out.println("Using GenericSerializer to write json data takes " + (System.nanoTime() - start) / (1000 * 1000) + " ms.");
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
String.format(JSON, groupList);
}
System.out.println("Using StringFormat to write json data takes " + (System.nanoTime() - start) / (1000 * 1000) + " ms.");
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
GenericSerializer.writeXml(groupList);
}
System.out.println("Using GenericSerializer to write xml data takes " + (System.nanoTime() - start) / (1000 * 1000) + " ms.");
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
String.format(XML, groupList);
}
System.out.println("Using StringFormat to write xml data takes " + (System.nanoTime() - start) / (1000 * 1000) + " ms.");
}
}