package com.cloud.api.response; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.log4j.Logger; import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiGsonHelper; import com.cloud.api.BaseCmd; import com.cloud.api.ResponseObject; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; public class ApiResponseSerializer { private static final Logger s_logger = Logger.getLogger(ApiResponseSerializer.class.getName()); public static String toSerializedString(ResponseObject result, String responseType) { if (BaseCmd.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) { return toJSONSerializedString(result); } else { return toXMLSerializedString(result); } } private static final Pattern s_unicodeEscapePattern = Pattern.compile("\\\\u([0-9A-Fa-f]{4})"); public static String unescape(String escaped) { String str = escaped; Matcher matcher = s_unicodeEscapePattern.matcher(str); while(matcher.find()) { str = str.replaceAll("\\" + matcher.group(0), Character.toString((char)Integer.parseInt(matcher.group(1), 16))); } return str; } private static String toJSONSerializedString(ResponseObject result) { if (result != null) { Gson gson = ApiGsonHelper.getBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); StringBuilder sb = new StringBuilder(); sb.append("{ \"" + result.getResponseName() + "\" : "); if (result instanceof ListResponse) { List responses = ((ListResponse)result).getResponses(); if ((responses != null) && !responses.isEmpty()) { int count = responses.size(); String jsonStr = gson.toJson(responses.get(0));; jsonStr = unescape(jsonStr); sb.append("{ \"" + responses.get(0).getObjectName() + "\" : [ " + jsonStr); for (int i = 1; i < count; i++) { jsonStr = gson.toJson(responses.get(i)); jsonStr = unescape(jsonStr); sb.append(", " + jsonStr); } sb.append(" ] }"); } else { sb.append("{ }"); } } else if (result instanceof SuccessResponse) { sb.append("{ \"success\" : \""+((SuccessResponse)result).getSuccess()+"\"} "); } else if (result instanceof ExceptionResponse) { sb.append("{\"errorcode\" : "+((ExceptionResponse)result).getErrorCode() + ", \"errortext\" : \""+ ((ExceptionResponse)result).getErrorText() +"\"} "); }else { String jsonStr = gson.toJson(result); if ((jsonStr != null) && !"".equals(jsonStr)) { jsonStr = unescape(jsonStr); if (result instanceof AsyncJobResponse || result instanceof CreateCmdResponse) { sb.append(jsonStr); } else { sb.append(" { \"" + result.getObjectName() + "\" : " + jsonStr + " } "); } } else { sb.append("{ }"); } } sb.append(" }"); return sb.toString(); } return null; } private static String toXMLSerializedString(ResponseObject result) { StringBuilder sb = new StringBuilder(); sb.append(""); sb.append("<" + result.getResponseName() + " cloud-stack-version=\""+ApiDBUtils.getVersion()+ "\">"); if (result instanceof ListResponse) { List responses = ((ListResponse)result).getResponses(); if ((responses != null) && !responses.isEmpty()) { for (ResponseObject obj : responses) { serializeResponseObjXML(sb, obj); } } } else { if (result instanceof CreateCmdResponse || result instanceof AsyncJobResponse) { serializeResponseObjFieldsXML(sb, result); } else { serializeResponseObjXML(sb, result); } } sb.append(""); return sb.toString(); } private static void serializeResponseObjXML(StringBuilder sb, ResponseObject obj){ if (!(obj instanceof SuccessResponse)&& !(obj instanceof ExceptionResponse)){ sb.append("<" + obj.getObjectName() + ">"); } serializeResponseObjFieldsXML(sb, obj); if (!(obj instanceof SuccessResponse) && !(obj instanceof ExceptionResponse)){ sb.append(""); } } private static void serializeResponseObjFieldsXML(StringBuilder sb, ResponseObject obj){ boolean isAsync = false; if (obj instanceof AsyncJobResponse) isAsync = true; Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { if ((field.getModifiers() & Modifier.TRANSIENT) != 0) { continue; // skip transient fields } SerializedName serializedName = field.getAnnotation(SerializedName.class); if (serializedName == null) { continue; // skip fields w/o serialized name } String propName = field.getName(); Method method = getGetMethod(obj, propName); if (method != null) { try { Object fieldValue = method.invoke(obj); if (fieldValue != null) { if (fieldValue instanceof ResponseObject) { ResponseObject subObj = (ResponseObject)fieldValue; if (isAsync) { sb.append(""); } serializeResponseObjXML(sb, subObj); if (isAsync) { sb.append(""); } } else if (fieldValue instanceof List){ List subResponseList = (List)fieldValue; for (Object value: subResponseList) { if (value instanceof ResponseObject){ ResponseObject subObj = (ResponseObject)value; if (serializedName != null) { subObj.setObjectName(serializedName.value()); } serializeResponseObjXML(sb, subObj); } } }else if (fieldValue instanceof Date) { sb.append("<" + serializedName.value() + ">" + BaseCmd.getDateString((Date)fieldValue) + ""); } else { sb.append("<" + serializedName.value() + ">" + fieldValue.toString() + ""); } } } catch (IllegalArgumentException e) { s_logger.error("Illegal argument exception when calling ResponseObject " + obj.getClass().getName() + " get method for property: " + propName); } catch (IllegalAccessException e) { s_logger.error("Illegal access exception when calling ResponseObject " + obj.getClass().getName() + " get method for property: " + propName); } catch (InvocationTargetException e) { s_logger.error("Invocation target exception when calling ResponseObject " + obj.getClass().getName() + " get method for property: " + propName); } } } } private static Method getGetMethod(Object o, String propName) { Method method = null; String methodName = getGetMethodName("get", propName); try { method = o.getClass().getMethod(methodName); } catch (SecurityException e1) { s_logger.error("Security exception in getting ResponseObject " + o.getClass().getName() + " get method for property: " + propName); } catch (NoSuchMethodException e1) { if (s_logger.isTraceEnabled()) { s_logger.trace("ResponseObject " + o.getClass().getName() + " does not have " + methodName + "() method for property: " + propName + ", will check is-prefixed method to see if it is boolean property"); } } if (method != null) return method; methodName = getGetMethodName("is", propName); try { method = o.getClass().getMethod(methodName); } catch (SecurityException e1) { s_logger.error("Security exception in getting ResponseObject " + o.getClass().getName() + " get method for property: " + propName); } catch (NoSuchMethodException e1) { s_logger.warn("ResponseObject " + o.getClass().getName() + " does not have " + methodName + "() method for property: " + propName); } return method; } private static String getGetMethodName(String prefix, String fieldName) { StringBuffer sb = new StringBuffer(prefix); if (fieldName.length() >= prefix.length() && fieldName.substring(0, prefix.length()).equals(prefix)) { return fieldName; } else { sb.append(fieldName.substring(0, 1).toUpperCase()); sb.append(fieldName.substring(1)); } return sb.toString(); } }