/******************************************************************************* * Copyright (c) 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Jan S. Rellermeyer, IBM Research - initial API and implementation *******************************************************************************/ package org.osgi.impl.service.rest; import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.SchemaFactory; import org.json.JSONObject; import org.osgi.impl.service.rest.pojos.BundleExceptionPojo; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Reflector to create pojos from JSON Object representations and vice versa. * * @author Jan S. Rellermeyer, IBM Research * @param <B> The pojo base class for which the reflector does the conversion. */ public class PojoReflector<B> { private static HashMap<Class<?>, PojoReflector<?>> reflectorCache = new HashMap<Class<?>, PojoReflector<?>>(); private final Class<B> clazz; private final HashMap<String, Method> setterMethodTable; private final HashMap<String, Method> getterMethodTable; private static final DocumentBuilderFactory factory; private static final Map<Class<?>, String> typeCache = new HashMap<Class<?>, String>(); private static final String SCHEMA_LOCATION = "http://www.osgi.org/xmlns/rest/v1.0.0 rest.xsd"; private static final String REST_NS = "rest"; static { final SchemaFactory sfact = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); factory = DocumentBuilderFactory.newInstance(); try { factory.setSchema(sfact.newSchema(new StreamSource(PojoReflector.class.getResourceAsStream("/rest.xsd")))); factory.setNamespaceAware(true); factory.setValidating(true); } catch (SAXException e) { e.printStackTrace(); } typeCache.put(String.class, "String"); typeCache.put(Long.class, "Long"); typeCache.put(Double.class, "Double"); typeCache.put(Float.class, "Float"); typeCache.put(Integer.class, "Integer"); typeCache.put(Byte.class, "Byte"); typeCache.put(Character.class, "Character"); typeCache.put(Boolean.class, "Boolean"); typeCache.put(Short.class, "Short"); } public static <T> PojoReflector<T> getReflector(final Class<T> clazz) { @SuppressWarnings("unchecked") PojoReflector<T> r = (PojoReflector<T>) reflectorCache.get(clazz); if (r == null) { r = new PojoReflector<T>(clazz); reflectorCache.put(clazz, r); } return r; } private PojoReflector(final Class<B> clazz) { this.clazz = clazz; final Field[] fields = clazz.getDeclaredFields(); setterMethodTable = new HashMap<String, Method>(fields.length); getterMethodTable = new HashMap<String, Method>(fields.length); for (int i = 0; i < fields.length; i++) { final Field field = fields[i]; final String fieldName = field.getName(); try { final Method setter = clazz.getMethod(getSetterName(fieldName), field.getType()); setterMethodTable.put(fieldName, setter); final Method getter = clazz.getMethod(getGetterName(fieldName)); getterMethodTable.put(fieldName, getter); } catch (final NoSuchMethodException e) { e.printStackTrace(); } } } private static String getSetterName(final String fieldName) { return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); } private static String getGetterName(final String fieldName) { return "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); } public B beanFromJSONObject(final JSONObject obj) throws Exception { final String[] names = JSONObject.getNames(obj); final B instance = clazz.newInstance(); for (int i = 0; i < names.length; i++) { final String key = names[i]; final Method setter = setterMethodTable.get(key); if (setter == null) { // silently ignore, it's JSON after all continue; } final Object o = obj.get(key); // check for empty object from JS... if (!(o instanceof JSONObject)) { setter.invoke(instance, o); } } return instance; } public B beanFromXml(final Document doc) throws Exception { final B instance = clazz.newInstance(); final Node rootNode = doc.getFirstChild(); final NodeList elems = rootNode.getChildNodes(); for (int i = 0; i < elems.getLength(); i++) { final String key = elems.item(i).getNodeName(); final Method setter = setterMethodTable.get(key); if (setter == null) { System.err.println("Warning: unknown value " + key); continue; } final String value = elems.item(i).getTextContent(); final Object o; final Class<?> type = setter.getParameterTypes()[0]; if (int.class == type) { o = Integer.valueOf(value); } else if (long.class == type) { o = Long.valueOf(value); } else if (boolean.class == type) { o = Boolean.valueOf(value); } else { o = value; } setter.invoke(instance, o); } return instance; } public Document xmlFromBean(final B bean) throws Exception { final DocumentBuilder builder = factory.newDocumentBuilder(); final Document doc = builder.newDocument(); final Element rootNode = xmlFromBean(bean, doc); rootNode.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:schemaLocation", SCHEMA_LOCATION); doc.appendChild(rootNode); return doc; } public Element xmlFromBean(final Object bean, final Document doc) throws Exception { final Element rootNode = doc.createElementNS(REST_NS, bean.getClass().getAnnotation(RootNode.class).name()); if (bean instanceof Collection) { String elemName = null; boolean complex = false; final ElementNode a = bean.getClass().getAnnotation(ElementNode.class); if (a != null) { elemName = a.name(); } for (final Object o : (Collection<?>) bean) { if (elemName == null) { elemName = o.getClass().getAnnotation(RootNode.class).name(); complex = true; } if (complex) { rootNode.appendChild(getReflector(o.getClass()).xmlFromBean(o, doc)); } else { rootNode.appendChild(toXml(o, elemName, doc)); } } } else if (bean instanceof BundleExceptionPojo) { final BundleExceptionPojo p = (BundleExceptionPojo) bean; final Element tc = doc.createElementNS(REST_NS, "typecode"); tc.setTextContent(Integer.toString(p.getTypecode())); rootNode.appendChild(tc); final Element msg = doc.createElementNS(REST_NS, "message"); msg.setTextContent(p.getMessage()); rootNode.appendChild(msg); } else { for (final Map.Entry<String, Method> entry : getterMethodTable.entrySet()) { final String field = entry.getKey(); final Object o = entry.getValue().invoke(bean); rootNode.appendChild(toXml(o, field, doc)); } } return rootNode; } // for debugging only @SuppressWarnings("unused") private final String printDoc(final Document doc) throws Exception { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); return writer.getBuffer().toString(); } private static Element toXml(final Object o, final String name, final Document doc) { final Element e = doc.createElementNS(REST_NS, name); if ("usingBundles".equals(name)) { for (final String bundle : (String[]) o) { e.appendChild(toXml(bundle, "bundle", doc)); } } else if (o instanceof Map) { @SuppressWarnings("unchecked") final Map<Object, Object> map = (Map<Object, Object>) o; for (final Map.Entry<Object, Object> entry : map.entrySet()) { final Element elem = doc.createElementNS(REST_NS, "property"); elem.setAttribute("name", entry.getKey().toString()); final Object val = entry.getValue(); if (val.getClass().isArray()) { final String type = getType(o.getClass().getComponentType()); if (type != null) { e.setAttribute("type", type); } final int len = Array.getLength(val); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i++) { sb.append(Array.get(val, i).toString()); if (i < len) { sb.append('\n'); } } elem.setTextContent(sb.toString()); } else { final String type = getType(val.getClass()); if (type != null) { elem.setAttribute("type", type); } elem.setAttribute("value", val.toString()); } e.appendChild(elem); } } else { e.setTextContent(o.toString()); } return e; } private static String getType(Class<? extends Object> cls) { return typeCache.get(cls); } public static Document mapToXml(final Map<String, String> map) throws Exception { final DocumentBuilder builder = factory.newDocumentBuilder(); final Document doc = builder.newDocument(); final Element rootNode = doc.createElementNS(REST_NS, "bundleHeader"); doc.appendChild(rootNode); for (final String key : map.keySet()) { final Element entry = doc.createElementNS(REST_NS, "entry"); entry.setAttribute("key", key); entry.setAttribute("value", map.get(key)); rootNode.appendChild(entry); } return doc; } @Retention(RetentionPolicy.RUNTIME) public @interface RootNode { String name(); } @Retention(RetentionPolicy.RUNTIME) public @interface ElementNode { String name(); } }