All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.objecttools.ObjectRest Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.objecttools;

import static java.net.HttpURLConnection.*;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;

/**
 * POJO REST API.
 *
 * 

* Provides the ability to perform standard REST operations (GET, PUT, POST, DELETE) against nodes in a POJO model. * Nodes in the POJO model are addressed using URLs. * *

* A POJO model is defined as a tree model where nodes consist of consisting of the following: *

    *
  • * {@link Map Maps} and Java beans representing JSON objects. *
  • * {@link Collection Collections} and arrays representing JSON arrays. *
  • * Java beans. *
* *

* Leaves of the tree can be any type of object. * *

* Use {@link #get(String) get()} to retrieve an element from a JSON tree. *
Use {@link #put(String,Object) put()} to create (or overwrite) an element in a JSON tree. *
Use {@link #post(String,Object) post()} to add an element to a list in a JSON tree. *
Use {@link #delete(String) delete()} to remove an element from a JSON tree. * *

* Leading slashes in URLs are ignored. * So "/xxx/yyy/zzz" and "xxx/yyy/zzz" are considered identical. * *

Example:
*

* // Construct an unstructured POJO model * JsonMap map = JsonMap.ofJson("" * + "{" * + " name:'John Smith', " * + " address:{ " * + " streetAddress:'21 2nd Street', " * + " city:'New York', " * + " state:'NY', " * + " postalCode:10021 " * + " }, " * + " phoneNumbers:[ " * + " '212 555-1111', " * + " '212 555-2222' " * + " ], " * + " additionalInfo:null, " * + " remote:false, " * + " height:62.4, " * + " 'fico score':' > 640' " * + "} " * ); * * // Wrap Map inside an ObjectRest object * ObjectRest johnSmith = ObjectRest.create(map); * * // Get a simple value at the top level * // "John Smith" * String name = johnSmith.getString("name"); * * // Change a simple value at the top level * johnSmith.put("name", "The late John Smith"); * * // Get a simple value at a deep level * // "21 2nd Street" * String streetAddress = johnSmith.getString("address/streetAddress"); * * // Set a simple value at a deep level * johnSmith.put("address/streetAddress", "101 Cemetery Way"); * * // Get entries in a list * // "212 555-1111" * String firstPhoneNumber = johnSmith.getString("phoneNumbers/0"); * * // Add entries to a list * johnSmith.post("phoneNumbers", "212 555-3333"); * * // Delete entries from a model * johnSmith.delete("fico score"); * * // Add entirely new structures to the tree * JsonMap medicalInfo = JsonMap.ofJson("" * + "{" * + " currentStatus: 'deceased'," * + " health: 'non-existent'," * + " creditWorthiness: 'not good'" * + "}" * ); * johnSmith.put("additionalInfo/medicalInfo", medicalInfo); *

* *

* In the special case of collections/arrays of maps/beans, a special XPath-like selector notation can be used in lieu * of index numbers on GET requests to return a map/bean with a specified attribute value. *
The syntax is {@code @attr=val}, where attr is the attribute name on the child map, and val is the matching value. * *

Example:
*

* // Get map/bean with name attribute value of 'foo' from a list of items * Map map = objectRest.getMap("/items/@name=foo"); *

* *
See Also:
    *
*/ @SuppressWarnings({"unchecked","rawtypes"}) public final class ObjectRest { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- /** The list of possible request types. */ private static final int GET=1, PUT=2, POST=3, DELETE=4; /** * Static creator. * @param o The object being wrapped. * @return A new {@link ObjectRest} object. */ public static ObjectRest create(Object o) { return new ObjectRest(o); } /** * Static creator. * @param o The object being wrapped. * @param parser The parser to use for parsing arguments and converting objects to the correct data type. * @return A new {@link ObjectRest} object. */ public static ObjectRest create(Object o, ReaderParser parser) { return new ObjectRest(o, parser); } //----------------------------------------------------------------------------------------------------------------- // Instance //----------------------------------------------------------------------------------------------------------------- private ReaderParser parser = JsonParser.DEFAULT; final BeanSession session; /** If true, the root cannot be overwritten */ private boolean rootLocked = false; /** The root of the model. */ private JsonNode root; /** * Create a new instance of a REST interface over the specified object. * *

* Uses {@link BeanContext#DEFAULT} for working with Java beans. * * @param o The object to be wrapped. */ public ObjectRest(Object o) { this(o, null); } /** * Create a new instance of a REST interface over the specified object. * *

* The parser is used as the bean context. * * @param o The object to be wrapped. * @param parser The parser to use for parsing arguments and converting objects to the correct data type. */ public ObjectRest(Object o, ReaderParser parser) { this.session = parser == null ? BeanContext.DEFAULT_SESSION : parser.getBeanContext().getSession(); if (parser == null) parser = JsonParser.DEFAULT; this.parser = parser; this.root = new JsonNode(null, null, o, session.object()); } /** * Call this method to prevent the root object from being overwritten on put("", xxx); calls. * * @return This object. */ public ObjectRest setRootLocked() { this.rootLocked = true; return this; } /** * The root object that was passed into the constructor of this method. * * @return The root object. */ public Object getRootObject() { return root.o; } /** * Retrieves the element addressed by the URL. * * @param url * The URL of the element to retrieve. *
If null or blank, returns the root. * @return The addressed element, or null if that element does not exist in the tree. */ public Object get(String url) { return getWithDefault(url, null); } /** * Retrieves the element addressed by the URL. * * @param url * The URL of the element to retrieve. *
If null or blank, returns the root. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The addressed element, or null if that element does not exist in the tree. */ public Object getWithDefault(String url, Object defVal) { Object o = service(GET, url, null); return o == null ? defVal : o; } /** * Retrieves the element addressed by the URL as the specified object type. * *

* Will convert object to the specified type per {@link BeanSession#convertToType(Object, Class)}. * *

Examples:
*

* ObjectRest objectRest = new ObjectRest(object); * * // Value converted to a string. * String string = objectRest.get("path/to/string", String.class); * * // Value converted to a bean. * MyBean bean = objectRest.get("path/to/bean", MyBean.class); * * // Value converted to a bean array. * MyBean[] beanArray = objectRest.get("path/to/beanarray", MyBean[].class); * * // Value converted to a linked-list of objects. * List list = objectRest.get("path/to/list", LinkedList.class); * * // Value converted to a map of object keys/values. * Map map = objectRest.get("path/to/map", TreeMap.class); *

* * @param url * The URL of the element to retrieve. * If null or blank, returns the root. * @param type The specified object type. * * @param The specified object type. * @return The addressed element, or null if that element does not exist in the tree. */ public T get(String url, Class type) { return getWithDefault(url, null, type); } /** * Retrieves the element addressed by the URL as the specified object type. * *

* Will convert object to the specified type per {@link BeanSession#convertToType(Object, Class)}. * *

* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). * *

Examples:
*

* ObjectRest objectRest = new ObjectRest(object); * * // Value converted to a linked-list of strings. * List<String> list1 = objectRest.get("path/to/list1", LinkedList.class, String.class); * * // Value converted to a linked-list of beans. * List<MyBean> list2 = objectRest.get("path/to/list2", LinkedList.class, MyBean.class); * * // Value converted to a linked-list of linked-lists of strings. * List<List<String>> list3 = objectRest.get("path/to/list3", LinkedList.class, LinkedList.class, String.class); * * // Value converted to a map of string keys/values. * Map<String,String> map1 = objectRest.get("path/to/map1", TreeMap.class, String.class, String.class); * * // Value converted to a map containing string keys and values of lists containing beans. * Map<String,List<MyBean>> map2 = objectRest.get("path/to/map2", TreeMap.class, String.class, List.class, MyBean.class); *

* *

* Collection classes are assumed to be followed by zero or one objects indicating the element type. * *

* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types. * *

* The array can be arbitrarily long to indicate arbitrarily complex data structures. * *

Notes:
    *
  • * Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection. *
* * @param url * The URL of the element to retrieve. * If null or blank, returns the root. * @param type The specified object type. * @param args The specified object parameter types. * * @param The specified object type. * @return The addressed element, or null if that element does not exist in the tree. */ public T get(String url, Type type, Type...args) { return getWithDefault(url, null, type, args); } /** * Same as {@link #get(String, Class)} but returns a default value if the addressed element is null or non-existent. * * @param url * The URL of the element to retrieve. * If null or blank, returns the root. * @param def The default value if addressed item does not exist. * @param type The specified object type. * * @param The specified object type. * @return The addressed element, or null if that element does not exist in the tree. */ public T getWithDefault(String url, T def, Class type) { Object o = service(GET, url, null); if (o == null) return def; return session.convertToType(o, type); } /** * Same as {@link #get(String,Type,Type[])} but returns a default value if the addressed element is null or non-existent. * * @param url * The URL of the element to retrieve. * If null or blank, returns the root. * @param def The default value if addressed item does not exist. * @param type The specified object type. * @param args The specified object parameter types. * * @param The specified object type. * @return The addressed element, or null if that element does not exist in the tree. */ public T getWithDefault(String url, T def, Type type, Type...args) { Object o = service(GET, url, null); if (o == null) return def; return session.convertToType(o, type, args); } /** * Returns the specified entry value converted to a {@link String}. * *

* Shortcut for get(String.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. */ public String getString(String url) { return get(url, String.class); } /** * Returns the specified entry value converted to a {@link String}. * *

* Shortcut for get(String.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. */ public String getString(String url, String defVal) { return getWithDefault(url, defVal, String.class); } /** * Returns the specified entry value converted to an {@link Integer}. * *

* Shortcut for get(Integer.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer getInt(String url) { return get(url, Integer.class); } /** * Returns the specified entry value converted to an {@link Integer}. * *

* Shortcut for get(Integer.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer getInt(String url, Integer defVal) { return getWithDefault(url, defVal, Integer.class); } /** * Returns the specified entry value converted to a {@link Long}. * *

* Shortcut for get(Long.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Long getLong(String url) { return get(url, Long.class); } /** * Returns the specified entry value converted to a {@link Long}. * *

* Shortcut for get(Long.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Long getLong(String url, Long defVal) { return getWithDefault(url, defVal, Long.class); } /** * Returns the specified entry value converted to a {@link Boolean}. * *

* Shortcut for get(Boolean.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean getBoolean(String url) { return get(url, Boolean.class); } /** * Returns the specified entry value converted to a {@link Boolean}. * *

* Shortcut for get(Boolean.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean getBoolean(String url, Boolean defVal) { return getWithDefault(url, defVal, Boolean.class); } /** * Returns the specified entry value converted to a {@link Map}. * *

* Shortcut for get(Map.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(String url) { return get(url, Map.class); } /** * Returns the specified entry value converted to a {@link Map}. * *

* Shortcut for get(Map.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(String url, Map defVal) { return getWithDefault(url, defVal, Map.class); } /** * Returns the specified entry value converted to a {@link List}. * *

* Shortcut for get(List.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(String url) { return get(url, List.class); } /** * Returns the specified entry value converted to a {@link List}. * *

* Shortcut for get(List.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(String url, List defVal) { return getWithDefault(url, defVal, List.class); } /** * Returns the specified entry value converted to a {@link Map}. * *

* Shortcut for get(JsonMap.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public JsonMap getJsonMap(String url) { return get(url, JsonMap.class); } /** * Returns the specified entry value converted to a {@link JsonMap}. * *

* Shortcut for get(JsonMap.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public JsonMap getJsonMap(String url, JsonMap defVal) { return getWithDefault(url, defVal, JsonMap.class); } /** * Returns the specified entry value converted to a {@link JsonList}. * *

* Shortcut for get(JsonList.class, key). * * @param url The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public JsonList getJsonList(String url) { return get(url, JsonList.class); } /** * Returns the specified entry value converted to a {@link JsonList}. * *

* Shortcut for get(JsonList.class, key, defVal). * * @param url The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public JsonList getJsonList(String url, JsonList defVal) { return getWithDefault(url, defVal, JsonList.class); } /** * Executes the specified method with the specified parameters on the specified object. * * @param url The URL of the element to retrieve. * @param method * The method signature. *

* Can be any of the following formats: *

    *
  • * Method name only. e.g. "myMethod". *
  • * Method name with class names. e.g. "myMethod(String,int)". *
  • * Method name with fully-qualified class names. e.g. "myMethod(java.util.String,int)". *
*

* As a rule, use the simplest format needed to uniquely resolve a method. * @param args * The arguments to pass as parameters to the method. * These will automatically be converted to the appropriate object type if possible. * This must be an array, like a JSON array. * @return The returned object from the method call. * @throws ExecutableException Exception occurred on invoked constructor/method/field. * @throws ParseException Malformed input encountered. * @throws IOException Thrown by underlying stream. */ public Object invokeMethod(String url, String method, String args) throws ExecutableException, ParseException, IOException { try { return new ObjectIntrospector(get(url), parser).invokeMethod(method, args); } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { throw new ExecutableException(e); } } /** * Returns the list of available methods that can be passed to the {@link #invokeMethod(String, String, String)} * for the object addressed by the specified URL. * * @param url The URL. * @return The list of methods. */ public Collection getPublicMethods(String url) { Object o = get(url); if (o == null) return null; return session.getClassMeta(o.getClass()).getPublicMethods().keySet(); } /** * Returns the class type of the object at the specified URL. * * @param url The URL. * @return The class type. */ public ClassMeta getClassMeta(String url) { JsonNode n = getNode(normalizeUrl(url), root); if (n == null) return null; return n.cm; } /** * Sets/replaces the element addressed by the URL. * *

* This method expands the POJO model as necessary to create the new element. * * @param url * The URL of the element to create. * If null or blank, the root itself is replaced with the specified value. * @param val The value being set. Value can be of any type. * @return The previously addressed element, or null the element did not previously exist. */ public Object put(String url, Object val) { return service(PUT, url, val); } /** * Adds a value to a list element in a POJO model. * *

* The URL is the address of the list being added to. * *

* If the list does not already exist, it will be created. * *

* This method expands the POJO model as necessary to create the new element. * *

Notes:
    *
  • * You can only post to three types of nodes: *
      *
    • {@link List Lists} *
    • {@link Map Maps} containing integers as keys (i.e sparse arrays) *
    • arrays *
    *
* * @param url * The URL of the element being added to. * If null or blank, the root itself (assuming it's one of the types specified above) is added to. * @param val The value being added. * @return The URL of the element that was added. */ public String post(String url, Object val) { return (String)service(POST, url, val); } /** * Remove an element from a POJO model. * *

* If the element does not exist, no action is taken. * * @param url * The URL of the element being deleted. * If null or blank, the root itself is deleted. * @return The removed element, or null if that element does not exist. */ public Object delete(String url) { return service(DELETE, url, null); } @Override /* Object */ public String toString() { return String.valueOf(root.o); } /** Handle nulls and strip off leading '/' char. */ private static String normalizeUrl(String url) { // Interpret nulls and blanks the same (i.e. as addressing the root itself) if (url == null) url = ""; // Strip off leading slash if present. if (url.length() > 0 && url.charAt(0) == '/') url = url.substring(1); return url; } /* * Workhorse method. */ private Object service(int method, String url, Object val) throws ObjectRestException { url = normalizeUrl(url); if (method == GET) { JsonNode p = getNode(url, root); return p == null ? null : p.o; } // Get the url of the parent and the property name of the addressed object. int i = url.lastIndexOf('/'); String parentUrl = (i == -1 ? null : url.substring(0, i)); String childKey = (i == -1 ? url : url.substring(i + 1)); if (method == PUT) { if (url.isEmpty()) { if (rootLocked) throw new ObjectRestException(HTTP_FORBIDDEN, "Cannot overwrite root object"); Object o = root.o; root = new JsonNode(null, null, val, session.object()); return o; } JsonNode n = (parentUrl == null ? root : getNode(parentUrl, root)); if (n == null) throw new ObjectRestException(HTTP_NOT_FOUND, "Node at URL ''{0}'' not found.", parentUrl); ClassMeta cm = n.cm; Object o = n.o; if (cm.isMap()) return ((Map)o).put(childKey, convert(val, cm.getValueType())); if (cm.isCollection() && o instanceof List) return ((List)o).set(parseInt(childKey), convert(val, cm.getElementType())); if (cm.isArray()) { o = setArrayEntry(n.o, parseInt(childKey), val, cm.getElementType()); ClassMeta pct = n.parent.cm; Object po = n.parent.o; if (pct.isMap()) { ((Map)po).put(n.keyName, o); return url; } if (pct.isBean()) { BeanMap m = session.toBeanMap(po); m.put(n.keyName, o); return url; } throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform PUT on ''{0}'' with parent node type ''{1}''", url, pct); } if (cm.isBean()) return session.toBeanMap(o).put(childKey, val); throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform PUT on ''{0}'' whose parent is of type ''{1}''", url, cm); } if (method == POST) { // Handle POST to root special if (url.isEmpty()) { ClassMeta cm = root.cm; Object o = root.o; if (cm.isCollection()) { Collection c = (Collection)o; c.add(convert(val, cm.getElementType())); return (c instanceof List ? url + "/" + (c.size()-1) : null); } if (cm.isArray()) { Object[] o2 = addArrayEntry(o, val, cm.getElementType()); root = new JsonNode(null, null, o2, null); return url + "/" + (o2.length-1); } throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform POST on ''{0}'' of type ''{1}''", url, cm); } JsonNode n = getNode(url, root); if (n == null) throw new ObjectRestException(HTTP_NOT_FOUND, "Node at URL ''{0}'' not found.", url); ClassMeta cm = n.cm; Object o = n.o; if (cm.isArray()) { Object[] o2 = addArrayEntry(o, val, cm.getElementType()); ClassMeta pct = n.parent.cm; Object po = n.parent.o; if (pct.isMap()) { ((Map)po).put(childKey, o2); return url + "/" + (o2.length-1); } if (pct.isBean()) { BeanMap m = session.toBeanMap(po); m.put(childKey, o2); return url + "/" + (o2.length-1); } throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform POST on ''{0}'' with parent node type ''{1}''", url, pct); } if (cm.isCollection()) { Collection c = (Collection)o; c.add(convert(val, cm.getElementType())); return (c instanceof List ? url + "/" + (c.size()-1) : null); } throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform POST on ''{0}'' of type ''{1}''", url, cm); } if (method == DELETE) { if (url.isEmpty()) { if (rootLocked) throw new ObjectRestException(HTTP_FORBIDDEN, "Cannot overwrite root object"); Object o = root.o; root = new JsonNode(null, null, null, session.object()); return o; } JsonNode n = (parentUrl == null ? root : getNode(parentUrl, root)); ClassMeta cm = n.cm; Object o = n.o; if (cm.isMap()) return ((Map)o).remove(childKey); if (cm.isCollection() && o instanceof List) return ((List)o).remove(parseInt(childKey)); if (cm.isArray()) { int index = parseInt(childKey); Object old = ((Object[])o)[index]; Object[] o2 = removeArrayEntry(o, index); ClassMeta pct = n.parent.cm; Object po = n.parent.o; if (pct.isMap()) { ((Map)po).put(n.keyName, o2); return old; } if (pct.isBean()) { BeanMap m = session.toBeanMap(po); m.put(n.keyName, o2); return old; } throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform POST on ''{0}'' with parent node type ''{1}''", url, pct); } if (cm.isBean()) return session.toBeanMap(o).put(childKey, null); throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot perform PUT on ''{0}'' whose parent is of type ''{1}''", url, cm); } return null; // Never gets here. } private Object[] setArrayEntry(Object o, int index, Object val, ClassMeta componentType) { Object[] a = (Object[])o; if (a.length <= index) { // Expand out the array. Object[] a2 = (Object[])Array.newInstance(a.getClass().getComponentType(), index+1); System.arraycopy(a, 0, a2, 0, a.length); a = a2; } a[index] = convert(val, componentType); return a; } private Object[] addArrayEntry(Object o, Object val, ClassMeta componentType) { Object[] a = (Object[])o; // Expand out the array. Object[] a2 = (Object[])Array.newInstance(a.getClass().getComponentType(), a.length+1); System.arraycopy(a, 0, a2, 0, a.length); a2[a.length] = convert(val, componentType); return a2; } private static Object[] removeArrayEntry(Object o, int index) { Object[] a = (Object[])o; // Shrink the array. Object[] a2 = (Object[])Array.newInstance(a.getClass().getComponentType(), a.length-1); System.arraycopy(a, 0, a2, 0, index); System.arraycopy(a, index+1, a2, index, a.length-index-1); return a2; } class JsonNode { Object o; ClassMeta cm; JsonNode parent; String keyName; JsonNode(JsonNode parent, String keyName, Object o, ClassMeta cm) { this.o = o; this.keyName = keyName; this.parent = parent; if (cm == null || cm.isObject()) { if (o == null) cm = session.object(); else cm = session.getClassMetaForObject(o); } this.cm = cm; } } JsonNode getNode(String url, JsonNode n) { if (url == null || url.isEmpty()) return n; int i = url.indexOf('/'); String parentKey, childUrl = null; if (i == -1) { parentKey = url; } else { parentKey = url.substring(0, i); childUrl = url.substring(i + 1); } Object o = n.o; Object o2 = null; ClassMeta cm = n.cm; ClassMeta ct2 = null; if (o == null) return null; if (cm.isMap()) { o2 = ((Map)o).get(parentKey); ct2 = cm.getValueType(); } else if (cm.isCollection() && o instanceof List) { int key = parseInt(parentKey); List l = ((List)o); if (l.size() <= key) return null; o2 = l.get(key); ct2 = cm.getElementType(); } else if (cm.isArray()) { int key = parseInt(parentKey); Object[] a = ((Object[])o); if (a.length <= key) return null; o2 = a[key]; ct2 = cm.getElementType(); } else if (cm.isBean()) { BeanMap m = session.toBeanMap(o); o2 = m.get(parentKey); BeanPropertyMeta pMeta = m.getPropertyMeta(parentKey); if (pMeta == null) throw new ObjectRestException(HTTP_BAD_REQUEST, "Unknown property ''{0}'' encountered while trying to parse into class ''{1}''", parentKey, m.getClassMeta() ); ct2 = pMeta.getClassMeta(); } if (childUrl == null) return new JsonNode(n, parentKey, o2, ct2); return getNode(childUrl, new JsonNode(n, parentKey, o2, ct2)); } private Object convert(Object in, ClassMeta cm) { if (cm == null) return in; if (cm.isBean() && in instanceof Map) return session.convertToType(in, cm); return in; } private static int parseInt(String key) { try { return Integer.parseInt(key); } catch (NumberFormatException e) { throw new ObjectRestException(HTTP_BAD_REQUEST, "Cannot address an item in an array with a non-integer key ''{0}''", key ); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy