/** * XWeb project * Created by Hamed Abdollahpour * https://github.com/abdollahpour/xweb */ package ir.xweb.data; import ir.xweb.util.MimeType; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; public class DataTools { private final static Logger logger = LoggerFactory.getLogger("DataTools"); public final static String PROPERTY_JSONP_CALLBACK = "jsonp.callback"; public final static String FORMAT_JSON = "json"; public final static String FORMAT_JSONP = "jsonp"; public final static String FORMAT_XML = "xml"; public final static String FORMAT_XML1 = "xml1"; public final static String FORMAT_XML2 = "xml2"; private final Map<String, ir.xweb.data.Formatter> formatters = new HashMap<String, ir.xweb.data.Formatter>(); private final HashMap<String, String> properties = new HashMap<String, String>(); private DateFormat dateFormat = new SimpleDateFormat(); public DataTools() { formatters.put(FORMAT_JSON, new JsonFormatter()); formatters.put(FORMAT_JSONP, new JsonpFormatter()); formatters.put(FORMAT_XML, new XmlFormatter()); formatters.put(FORMAT_XML1, new XmlFormatter()); formatters.put(FORMAT_XML2, new XmlFormatter2()); } public ir.xweb.data.Formatter addFormatter(final String name, final ir.xweb.data.Formatter formatter) { return formatters.put(name, formatter); } public ir.xweb.data.Formatter getFormatter(final String name) { final ir.xweb.data.Formatter formatter = formatters.get(name); return formatter; } public void setDateFormat(final DateFormat dateFormat) { if(dateFormat == null) { throw new IllegalArgumentException("null dataFormat"); } this.dateFormat = dateFormat; } public String addProperties(String property, String value) { if(value == null) { return properties.remove(property); } else { return properties.put(property, value); } } /** * Write parameter to an specific object * @param object * @param data * @param role * @throws IOException */ public void write( final Object object, final Map<String, ?> data, final String role) throws IOException { try { final List<?> keys = new ArrayList<Object>(data.keySet()); final Field[] fields = object.getClass().getFields(); for(Field f:fields) { String name = f.getName(); String validator = ""; if(f.isAnnotationPresent(XWebDataElement.class)) { final String aName = f.getAnnotation(XWebDataElement.class).key(); final String aRole = f.getAnnotation(XWebDataElement.class).write(); validator = f.getAnnotation(XWebDataElement.class).validator(); if(aName.length() > 0) { if(role != null && aRole.length() > 0 && !role.matches(aRole)) { throw new IllegalAccessException("No sufficient role. " + role + " not in " + aRole); } name = aName; } } if(keys.contains(name)) { final Object value = data.get(name); if(value != null) { final String s = value.toString(); // validate of require if(validator.length() > 0 && !s.matches(validator)) { throw new IllegalAccessException("Illegal value: " + value + " not match " + validator); } Class<?> type = f.getType(); if(type == String.class) { f.set(object, value.toString()); } else if(type == Integer.class || type == int.class) { if(value instanceof Integer) { f.set(object, value); } else { f.set(object, Integer.valueOf(s)); } } else if(type == Boolean.class || type == boolean.class) { if(value instanceof Boolean) { f.setBoolean(object, (Boolean)value); } else { f.setBoolean(object, Boolean.parseBoolean(s)); } } else if(type == Byte.class || type == byte.class) { f.set(object, (value instanceof Byte) ? value : Byte.parseByte(s)); } else if(type == Character.class || type == char.class) { if(s.length() != 0) { f.set(object, (value instanceof Character) ? value : s.charAt(0)); } } else if(type == Double.class || type == double.class) { f.set(object, (value instanceof Double) ? value : Double.parseDouble(s)); } else if(type == Float.class || type == float.class) { f.set(object, (value instanceof Float) ? value : Float.parseFloat(s)); } else if(type == Long.class || type == long.class) { f.set(object, (value instanceof Long) ? value : Long.parseLong(s)); } else if(type == Short.class || type == short.class) { f.set(object, (value instanceof Short) ? value : Short.parseShort(s)); } keys.remove(name); } } } final Method[] methods = object.getClass().getMethods(); for(Method m:methods) { String name = null; String validator = ""; if(m.isAnnotationPresent(XWebDataElement.class)) { final String aName = m.getAnnotation(XWebDataElement.class).key(); final String aRole = m.getAnnotation(XWebDataElement.class).write(); validator = m.getAnnotation(XWebDataElement.class).validator(); if(aName.length() > 0) { if(role != null && aRole.length() > 0 && !role.matches(aRole)) { throw new IllegalAccessException("No sufficient role. " + role + " not in " + aRole); } name = aName; } } Object value = null; if(name == null) { name = m.getName(); if(keys.contains(name)) { value = data.get(name); } else if(name.startsWith("set")) { // make setter function String setterName = name.substring(3); setterName = setterName.substring(0, 1).toLowerCase() + setterName.substring(1); value = data.get(setterName); } } else { value = data.get(name); } if(value != null) { final String s = value.toString(); // validate of require if(validator.length() > 0 && !s.matches(validator)) { throw new IllegalAccessException("Illegal value: " + value + " not match " + validator); } Class<?> type = m.getParameterTypes()[0]; if(type == String.class) { m.invoke(object, value.toString()); } else if(type == Integer.class || type == int.class) { m.invoke(object, (value instanceof Integer) ? value : Integer.parseInt(s)); } else if(type == Boolean.class || type == boolean.class) { m.invoke(object, (value instanceof Boolean) ? value : Boolean.parseBoolean(s)); } else if(type == Byte.class || type == byte.class) { m.invoke(object, (value instanceof Byte) ? value : Byte.valueOf(s).byteValue()); } else if(type == Character.class || type == char.class) { if(s.length() == 1) { m.invoke(object, s.charAt(0)); } } else if(type == Double.class || type == double.class) { m.invoke(object, (value instanceof Double) ? value : Double.parseDouble(s)); } else if(type == Float.class || type == float.class) { m.invoke(object, (value instanceof Float) ? value : Float.parseFloat(s)); } else if(type == Long.class || type == long.class) { m.invoke(object, (value instanceof Long) ? value : Long.parseLong(s)); } else if(type == Short.class || type == short.class) { m.invoke(object, (value instanceof Short) ? value : Short.parseShort(s)); } keys.remove(name); } } } catch (InvocationTargetException ex) { throw new IOException(ex); } catch (IllegalAccessException ex) { throw new IOException(ex); } } public void write( final HttpServletResponse response, final String format, final String role, final Object object) throws IOException { if(response == null) { throw new IllegalArgumentException("null response"); } ir.xweb.data.Formatter formatter = formatters.get(format); if(formatter != null) { if(!response.containsHeader("Content-Type")) { response.addHeader("Content-Type", formatter.getContentType()); response.setCharacterEncoding("UTF-8"); } } write(response.getWriter(), format, role, object); } public String write( final String format, final String role, final Object object) throws IOException { final StringWriter writer = new StringWriter(); write(writer, format, role, object); return writer.getBuffer().toString(); } public void write( final Writer writer, final String format, final String role, final Object object) throws IOException { write(writer, format, role, object, false); } public void write( final Writer writer, final String format, final String role, final Object object, final boolean expandListToParent) throws IOException { if(writer == null) { throw new IllegalArgumentException("null writer"); } if(format == null) { throw new IllegalArgumentException("null format"); } if(object == null) { throw new IllegalArgumentException("null object"); } final ir.xweb.data.Formatter formatter = formatters.get(format); if(formatter == null) { throw new IllegalArgumentException("Illegal formatter"); } try { // it's much faster to search for 'ROLE,' to split and check in array final Object formattedObject = convert(object, role == null ? null : (role + ",")); formatter.write(writer, formattedObject); writer.flush(); } catch (Exception ex) { throw new IOException(ex); } } private Object convert( final Object object, final String role) throws IOException, InvocationTargetException, IllegalAccessException { if(object == null) { return null; } final Class c = object.getClass(); final boolean isAnnoted = c.isAnnotationPresent(XWebData.class); if(isAnnoted) { final String name = object.getClass().getAnnotation(XWebData.class).name(); final AnnotedMap data = new AnnotedMap(name.length() == 0 ? object.getClass().getSimpleName() : name); final Method[] methods = c.getMethods(); for(Method m:methods) { if(m.isAnnotationPresent(XWebDataElement.class)) { if(m.getParameterTypes().length == 0) { final String aName = m.getAnnotation(XWebDataElement.class).key(); final String aRole = m.getAnnotation(XWebDataElement.class).read(); if(role == null || aRole.length() == 0 || role.matches(aRole)) { Object value = m.invoke(object); if(aName == null || aName.length() == 0) { data.put(m.getName(), convert(value, role)); } else { data.put(aName, convert(value, role)); } } } else { logger.warn("Error to get Method value, the method should not have any parameter"); } } } final Field[] fields = c.getFields(); for(Field f:fields) { if(f.isAnnotationPresent(XWebDataElement.class)) { final String aName = f.getAnnotation(XWebDataElement.class).key(); final String aRole = f.getAnnotation(XWebDataElement.class).read(); if(role == null || aRole.length() == 0 || role.matches(aRole)) { final Object value = f.get(object); if(value != null) { if(aName == null || aName.length() == 0) { data.put(f.getName(), convert(value, role)); } else { data.put(aName, convert(value, role)); } } } } } return data; } else if(object instanceof Map) { final Map<?, ?> map = (Map<?, ?>) object; final LinkedHashMap<String, Object> data = new LinkedHashMap<String, Object>(); for(Map.Entry<?, ?> e:map.entrySet()) { if(e.getValue() != null) { data.put(e.getKey().toString(), convert(e.getValue(), role)); } else { data.put(e.getKey().toString(), "null"); } } return data; } else if(object instanceof Collection) { final Collection<?> Collection = (Collection<?>) object; final List<Object> data = new ArrayList<Object>(); for(Object o:Collection) { data.add(convert(o, role)); } return data; } else if(object instanceof Date) { return dateFormat.format((Date) object); } return object; } private class JsonpFormatter extends JsonFormatter { private final String mimeType = MimeType.get("json"); @Override public void write(final Writer writer, final Object object) throws IOException { try { if(object instanceof Map) { final String callback = properties.get(PROPERTY_JSONP_CALLBACK); if(callback == null) { writer.write("jsonCallback("); } else { writer.append(callback).write("("); } ((JSONObject) write(object)).write(writer); writer.write(");"); } else { writer.write("jsonCallback("); ((JSONArray) write(object)).write(writer); writer.write(");"); } } catch (JSONException ex) { throw new IOException(ex); } } @Override public String getContentType() { return mimeType; } } }