http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/utils/PojoRest.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/PojoRest.java 
b/juneau-core/src/main/java/org/apache/juneau/utils/PojoRest.java
new file mode 100644
index 0000000..05f7d40
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/PojoRest.java
@@ -0,0 +1,847 @@
+/***************************************************************************************************************************
+ * 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.utils;
+
+import static java.net.HttpURLConnection.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * 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.
+ * <p>
+ *     A POJO model is defined as a tree model where nodes consist of 
consisting of the following:
+ *     <ul class='spaced-list'>
+ *             <li>{@link Map Maps} and Java beans representing JSON objects.
+ *             <li>{@link Collection Collections} and arrays representing JSON 
arrays.
+ *             <li>Java beans.
+ *     </ul>
+ * <p>
+ *     Leaves of the tree can be any type of object.
+ * <p>
+ *     Use {@link #get(String) get()} to retrieve an element from a JSON 
tree.<br>
+ *     Use {@link #put(String,Object) put()} to create (or overwrite) an 
element in a JSON tree.<br>
+ *     Use {@link #post(String,Object) post()} to add an element to a list in 
a JSON tree.<br>
+ *     Use {@link #delete(String) delete()} to remove an element from a JSON 
tree.<br>
+ * <p>
+ *     Leading slashes in URLs are ignored.  So <js>"/xxx/yyy/zzz"</js> and 
<js>"xxx/yyy/zzz"</js> are considered identical.
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ *     <jc>// Construct an unstructured POJO model</jc>
+ *     ObjectMap m = <jk>new</jk> ObjectMap(<js>""</js>
+ *             + <js>"{"</js>
+ *             + <js>" name:'John Smith', "</js>
+ *             + <js>" address:{ "</js>
+ *             + <js>"         streetAddress:'21 2nd Street', "</js>
+ *             + <js>"         city:'New York', "</js>
+ *             + <js>"         state:'NY', "</js>
+ *             + <js>"         postalCode:10021 "</js>
+ *             + <js>" }, "</js>
+ *             + <js>" phoneNumbers:[ "</js>
+ *             + <js>"         '212 555-1111', "</js>
+ *             + <js>"         '212 555-2222' "</js>
+ *             + <js>" ], "</js>
+ *             + <js>" additionalInfo:null, "</js>
+ *             + <js>" remote:false, "</js>
+ *             + <js>" height:62.4, "</js>
+ *             + <js>" 'fico score':' &gt; 640' "</js>
+ *             + <js>"} "</js>
+ *     );
+ *
+ *     <jc>// Wrap Map inside a PojoRest object</jc>
+ *     PojoRest johnSmith = <jk>new</jk> PojoRest(m);
+ *
+ *     <jc>// Get a simple value at the top level</jc>
+ *     <jc>// "John Smith"</jc>
+ *     String name = johnSmith.getString(<js>"name"</js>);
+ *
+ *     <jc>// Change a simple value at the top level</jc>
+ *     johnSmith.put(<js>"name"</js>, <js>"The late John Smith"</js>);
+ *
+ *     <jc>// Get a simple value at a deep level</jc>
+ *     <jc>// "21 2nd Street"</jc>
+ *     String streetAddress = 
johnSmith.getString(<js>"address/streetAddress"</js>);
+ *
+ *     <jc>// Set a simple value at a deep level</jc>
+ *     johnSmith.put(<js>"address/streetAddress"</js>, <js>"101 Cemetery 
Way"</js>);
+ *
+ *     <jc>// Get entries in a list</jc>
+ *     <jc>// "212 555-1111"</jc>
+ *     String firstPhoneNumber = 
johnSmith.getString(<js>"phoneNumbers/0"</js>);
+ *
+ *     <jc>// Add entries to a list</jc>
+ *     johnSmith.post(<js>"phoneNumbers"</js>, <js>"212 555-3333"</js>);
+ *
+ *     <jc>// Delete entries from a model</jc>
+ *     johnSmith.delete(<js>"fico score"</js>);
+ *
+ *     <jc>// Add entirely new structures to the tree</jc>
+ *     ObjectMap medicalInfo = new ObjectMap(<js>""</js>
+ *             + <js>"{"</js>
+ *             + <js>" currentStatus: 'deceased',"</js>
+ *             + <js>" health: 'non-existent',"</js>
+ *             + <js>" creditWorthiness: 'not good'"</js>
+ *             + <js>"}"</js>
+ *     );
+ *     johnSmith.put(<js>"additionalInfo/medicalInfo"</js>, medicalInfo);
+ * <p>
+ *     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.<br>
+ *     The syntax is {@code @attr=val}, where attr is the attribute name on 
the child map, and val is the matching value.
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ *     <jc>// Get map/bean with name attribute value of 'foo' from a list of 
items</jc>
+ *     Map m = pojoRest.getMap(<js>"/items/@name=foo"</js>);
+ * </p>
+ *
+ * @author James Bognar ([email protected])
+ */
+@SuppressWarnings({"unchecked","rawtypes"})
+public final class PojoRest {
+
+       /** The list of possible request types. */
+       private static final int GET=1, PUT=2, POST=3, DELETE=4;
+
+       private ReaderParser parser = JsonParser.DEFAULT;
+       private final BeanContext bc;
+
+       /** 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.
+        * <p>
+        *      Uses {@link BeanContext#DEFAULT} for working with Java beans.
+        *
+        * @param o The object to be wrapped.
+        */
+       public PojoRest(Object o) {
+               this(o, null);
+       }
+
+       /**
+        * Create a new instance of a REST interface over the specified object.
+        * <p>
+        *      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 PojoRest(Object o, ReaderParser parser) {
+               if (parser == null)
+                       parser = JsonParser.DEFAULT;
+               this.parser = parser;
+               this.bc = parser.getBeanContext();
+               this.root = new JsonNode(null, null, o, bc.object());
+       }
+
+       /**
+        * Call this method to prevent the root object from being overwritten 
on put("", xxx); calls.
+        *
+        * @return This object (for method chaining).
+        */
+       public PojoRest 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 get(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 get(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.
+        * <p>
+        * Will convert object to the specified type per {@link 
BeanContext#convertToType(Object, ClassMeta)}.
+        *
+        * @param type The specified object type.
+        * @param url The URL of the element to retrieve.
+        *              If null or blank, returns the root.
+        * @param <T> The specified object type.
+        *
+        * @return The addressed element, or null if that element does not 
exist in the tree.
+        */
+       public <T> T get(Class<T> type, String url) {
+               return get(type, url, null);
+       }
+
+       /**
+        * Retrieves the element addressed by the URL as the specified object 
type.
+        * <p>
+        * Will convert object to the specified type per {@link 
BeanContext#convertToType(Object, ClassMeta)}.
+        *
+        * @param type The specified object type.
+        * @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 <T> The specified object type.
+        *
+        * @return The addressed element, or null if that element does not 
exist in the tree.
+        */
+       public <T> T get(Class<T> type, String url, T def) {
+               Object o = service(GET, url, null);
+               if (o == null)
+                       return def;
+               return bc.convertToType(o, type);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link String}.
+        * <p>
+        *      Shortcut for <code>get(String.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        */
+       public String getString(String url) {
+               return get(String.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link String}.
+        * <p>
+        *      Shortcut for <code>get(String.<jk>class</jk>, key, 
defVal)</code>.
+        *
+        * @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 get(String.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to an {@link Integer}.
+        * <p>
+        *      Shortcut for <code>get(Integer.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public Integer getInt(String url) {
+               return get(Integer.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to an {@link Integer}.
+        * <p>
+        *      Shortcut for <code>get(Integer.<jk>class</jk>, key, 
defVal)</code>.
+        *
+        * @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 get(Integer.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Long}.
+        * <p>
+        *      Shortcut for <code>get(Long.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public Long getLong(String url) {
+               return get(Long.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Long}.
+        * <p>
+        *      Shortcut for <code>get(Long.<jk>class</jk>, key, defVal)</code>.
+        *
+        * @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 get(Long.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Boolean}.
+        * <p>
+        *      Shortcut for <code>get(Boolean.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public Boolean getBoolean(String url) {
+               return get(Boolean.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Boolean}.
+        * <p>
+        *      Shortcut for <code>get(Boolean.<jk>class</jk>, key, 
defVal)</code>.
+        *
+        * @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 get(Boolean.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Map}.
+        * <p>
+        *      Shortcut for <code>get(Map.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public Map<?,?> getMap(String url) {
+               return get(Map.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Map}.
+        * <p>
+        *      Shortcut for <code>get(Map.<jk>class</jk>, key, defVal)</code>.
+        *
+        * @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 get(Map.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link List}.
+        * <p>
+        *      Shortcut for <code>get(List.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public List<?> getList(String url) {
+               return get(List.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link List}.
+        * <p>
+        *      Shortcut for <code>get(List.<jk>class</jk>, key, defVal)</code>.
+        *
+        * @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 get(List.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link Map}.
+        * <p>
+        *      Shortcut for <code>get(ObjectMap.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public ObjectMap getObjectMap(String url) {
+               return get(ObjectMap.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link ObjectMap}.
+        * <p>
+        *      Shortcut for <code>get(ObjectMap.<jk>class</jk>, key, 
defVal)</code>.
+        *
+        * @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 ObjectMap getObjectMap(String url, ObjectMap defVal) {
+               return get(ObjectMap.class, url, defVal);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link ObjectList}.
+        * <p>
+        *      Shortcut for <code>get(ObjectList.<jk>class</jk>, key)</code>.
+        *
+        * @param url The key.
+        * @return The converted value, or <jk>null</jk> if the map contains no 
mapping for this key.
+        * @throws InvalidDataConversionException If value cannot be converted.
+        */
+       public ObjectList getObjectList(String url) {
+               return get(ObjectList.class, url);
+       }
+
+       /**
+        * Returns the specified entry value converted to a {@link ObjectList}.
+        * <p>
+        *      Shortcut for <code>get(ObjectList.<jk>class</jk>, key, 
defVal)</code>.
+        *
+        * @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 ObjectList getObjectList(String url, ObjectList defVal) {
+               return get(ObjectList.class, url, defVal);
+       }
+
+       /**
+        * 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.
+        *      <p>
+        *              Can be any of the following formats:
+        *      </p>
+        *      <ul class='spaced-list'>
+        *              <li>Method name only.  e.g. <js>"myMethod"</js>.
+        *              <li>Method name with class names.  e.g. 
<js>"myMethod(String,int)"</js>.
+        *              <li>Method name with fully-qualified class names.  e.g. 
<js>"myMethod(java.util.String,int)"</js>.
+        *      </ul>
+        *      <p>
+        *              As a rule, use the simplest format needed to uniquely 
resolve a method.
+        *      </p>
+        * @param args The arguments to pass as parameters to the method.<br>
+        *      These will automatically be converted to the appropriate object 
type if possible.<br>
+        *      This must be an array, like a JSON array.
+        * @return The returned object from the method call.
+        * @throws IllegalAccessException If the <code>Constructor</code> 
object enforces Java language access control and the underlying constructor is 
inaccessible.
+        * @throws IllegalArgumentException If one of the following occurs:
+        *      <ul class='spaced-list'>
+        *              <li>The number of actual and formal parameters differ.
+        *              <li>An unwrapping conversion for primitive arguments 
fails.
+        *              <li>A parameter value cannot be converted to the 
corresponding formal parameter type by a method invocation conversion.
+        *              <li>The constructor pertains to an enum type.
+        *      </ul>
+        * @throws InvocationTargetException If the underlying constructor 
throws an exception.
+        * @throws ParseException If the input contains a syntax error or is 
malformed.
+        * @throws NoSuchMethodException
+        * @throws IOException
+        */
+       public Object invokeMethod(String url, String method, String args) 
throws InvocationTargetException, IllegalArgumentException, 
IllegalAccessException, ParseException, NoSuchMethodException, IOException {
+               return new PojoIntrospector(get(url), 
parser).invokeMethod(method, args);
+       }
+
+       /**
+        * 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<String> getPublicMethods(String url) {
+               Object o = get(url);
+               if (o == null)
+                       return null;
+               return 
bc.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.
+        * <p>
+        *      This method expands the POJO model as necessary to create the 
new element.
+        *
+        * @param url The URL of the element to create.
+        *              If <jk>null</jk> 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 <jk>null</jk> 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.
+        * <p>
+        *      The URL is the address of the list being added to.
+        * <p>
+        *      If the list does not already exist, it will be created.
+        * <p>
+        *      This method expands the POJO model as necessary to create the 
new element.
+        * <p>
+        *      Note:  You can only post to three types of nodes:
+        *      <ul class='spaced-list'>
+        *              <li>{@link List Lists}
+        *              <li>{@link Map Maps} containing integers as keys (i.e 
sparse arrays)
+        *              <li>arrays
+        *      </ul>
+        *
+        * @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.
+        * <p>
+        * qIf the element does not exist, no action is taken.
+        *
+        * @param url The URL of the element being deleted.
+        *              If <jk>null</jk> 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 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 
PojoRestException {
+
+               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.length() == 0) {
+                               if (rootLocked)
+                                       throw new 
PojoRestException(HTTP_FORBIDDEN, "Cannot overwrite root object");
+                               Object o = root.o;
+                               root = new JsonNode(null, null, val, 
bc.object());
+                               return o;
+                       }
+                       JsonNode n = (parentUrl == null ? root : 
getNode(parentUrl, root));
+                       if (n == null)
+                               throw new PojoRestException(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 = bc.forBean(po);
+                                       m.put(n.keyName, o);
+                                       return url;
+                               }
+                               throw new PojoRestException(HTTP_BAD_REQUEST, 
"Cannot perform PUT on ''{0}'' with parent node type ''{1}''", url, pct);
+                       }
+                       if (cm.isBean())
+                               return bc.forBean(o).put(childKey, val);
+                       throw new PojoRestException(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.length() == 0) {
+                               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 PojoRestException(HTTP_BAD_REQUEST, 
"Cannot perform POST on ''{0}'' of type ''{1}''", url, cm);
+                       }
+                       JsonNode n = getNode(url, root);
+                       if (n == null)
+                               throw new PojoRestException(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 = bc.forBean(po);
+                                       m.put(childKey, o2);
+                                       return url + "/" + (o2.length-1);
+                               }
+                               throw new PojoRestException(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 PojoRestException(HTTP_BAD_REQUEST, "Cannot 
perform POST on ''{0}'' of type ''{1}''", url, cm);
+               }
+
+               if (method == DELETE) {
+                       if (url.length() == 0) {
+                               if (rootLocked)
+                                       throw new 
PojoRestException(HTTP_FORBIDDEN, "Cannot overwrite root object");
+                               Object o = root.o;
+                               root = new JsonNode(null, null, null, 
bc.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 = bc.forBean(po);
+                                       m.put(n.keyName, o2);
+                                       return old;
+                               }
+                               throw new PojoRestException(HTTP_BAD_REQUEST, 
"Cannot perform POST on ''{0}'' with parent node type ''{1}''", url, pct);
+                       }
+                       if (cm.isBean())
+                               return bc.forBean(o).put(childKey, null);
+                       throw new PojoRestException(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 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 = bc.object();
+                               else
+                                       cm = bc.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 = bc.forBean(o);
+                       o2 = m.get(parentKey);
+                       BeanPropertyMeta pMeta = m.getPropertyMeta(parentKey);
+                       if (pMeta == null)
+                               throw new PojoRestException(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 bc.convertToType(in, cm);
+               return in;
+       }
+
+       private int parseInt(String key) {
+               try {
+                       return Integer.parseInt(key);
+               } catch (NumberFormatException e) {
+                       throw new PojoRestException(HTTP_BAD_REQUEST,
+                               "Cannot address an item in an array with a 
non-integer key ''{0}''", key
+                       );
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/utils/PojoRestException.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/utils/PojoRestException.java 
b/juneau-core/src/main/java/org/apache/juneau/utils/PojoRestException.java
new file mode 100644
index 0000000..aeced12
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/PojoRestException.java
@@ -0,0 +1,60 @@
+/***************************************************************************************************************************
+ * 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.utils;
+
+import java.net.*;
+import java.text.*;
+
+/**
+ * Generic exception thrown from the {@link PojoRest} class.
+ * <p>
+ *     Typically, this is a user-error, such as trying to address a 
non-existent node in the tree.
+ * <p>
+ *     The status code is an HTTP-equivalent code.  It will be one of the 
following:
+ * <ul class='spaced-list'>
+ *     <li>{@link HttpURLConnection#HTTP_BAD_REQUEST HTTP_BAD_REQUEST} - 
Attempting to do something impossible.
+ *     <li>{@link HttpURLConnection#HTTP_NOT_FOUND HTTP_NOT_FOUND} - 
Attempting to access a non-existent node in the tree.
+ *     <li>{@link HttpURLConnection#HTTP_FORBIDDEN HTTP_FORBIDDEN} - 
Attempting to overwrite the root object.
+ * </ul>
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class PojoRestException extends RuntimeException {
+
+       private static final long serialVersionUID = 1L;
+
+       private int status;
+
+       /**
+        * Constructor.
+        *
+        * @param status The HTTP-equivalent status code.
+        * @param message The detailed message.
+        * @param args Optional message arguments.
+        */
+       public PojoRestException(int status, String message, Object...args) {
+               super(args.length == 0 ? message : 
MessageFormat.format(message, args));
+               this.status = status;
+       }
+
+       /**
+        * The HTTP-equivalent status code.
+        * <p>
+        *      See above for details.
+        *
+        * @return The HTTP-equivalent status code.
+        */
+       public int getStatus() {
+               return status;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/utils/ProcBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/ProcBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/utils/ProcBuilder.java
new file mode 100644
index 0000000..aa3ca4a
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/ProcBuilder.java
@@ -0,0 +1,387 @@
+/***************************************************************************************************************************
+ * 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.utils;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.logging.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.utils.IOPipe.*;
+
+/**
+ * Utility class for running operating system processes.
+ * <p>
+ * Similar to {@link java.lang.ProcessBuilder} but with additional features.
+ *
+ * @author James Bognar ([email protected])
+ */
+@SuppressWarnings("hiding")
+public class ProcBuilder {
+
+       private java.lang.ProcessBuilder pb = new java.lang.ProcessBuilder();
+       private TeeWriter outWriters = new TeeWriter(), logWriters = new 
TeeWriter();
+       private LineProcessor lp;
+       private Process p;
+       private int maxExitStatus = 0;
+       private boolean byLines;
+       private String divider = 
"--------------------------------------------------------------------------------";
+
+       /**
+        * Creates a process builder with the specified arguments.
+        * Equivalent to calling 
<code>ProcessBuilder.create().command(args);</code>
+        *
+        * @param args The command-line arguments.
+        * @return A new process builder.
+        */
+       public static ProcBuilder create(Object...args) {
+               return new ProcBuilder().command(args);
+       }
+
+       /**
+        * Creates an empty process builder.
+        *
+        * @return A new process builder.
+        */
+       public static ProcBuilder create() {
+               return new ProcBuilder().command();
+       }
+
+       /**
+        * Command arguments.
+        * Arguments can be collections or arrays and will be automatically 
expanded.
+        *
+        * @param args The command-line arguments.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder command(Object...args) {
+               return commandIf(ANY, args);
+       }
+
+       /**
+        * Command arguments if the specified matcher matches.
+        * Can be used for specifying os-specific commands.
+        * Example:
+        * <p class='bcode'>
+        *      ProcessBuilder pb = ProcessBuilder
+        *              .create()
+        *              .commandIf(<jsf>WINDOWS</jsf>, <js>"cmd /c dir"</js>)
+        *              .commandIf(<jsf>UNIX</jsf>, <js>"bash -c ls"</js>)
+        *              .merge()
+        *              .execute();
+        * </p>
+        *
+        * @param m The matcher.
+        * @param args The command line arguments if matcher matches.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder commandIf(Matcher m, Object...args) {
+               if (m.matches())
+                       pb.command(toList(args));
+               return this;
+       }
+
+       /**
+        * Append to the command arguments.
+        * Arguments can be collections or arrays and will be automatically 
expanded.
+        *
+        * @param args The command-line arguments.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder append(Object...args) {
+               return appendIf(ANY, args);
+       }
+
+       /**
+        * Append to the command arguments if the specified matcher matches.
+        * Arguments can be collections or arrays and will be automatically 
expanded.
+        *
+        * @param m The matcher.
+        * @param args The command line arguments if matcher matches.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder appendIf(Matcher m, Object...args) {
+               if (m.matches())
+                       pb.command().addAll(toList(args));
+               return this;
+       }
+
+       /**
+        * Merge STDOUT and STDERR into a single stream.
+        *
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder merge() {
+               pb.redirectErrorStream(true);
+               return this;
+       }
+
+       /**
+        * Use by-lines mode.
+        * Flushes output after every line of input.
+        *
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder byLines() {
+               this.byLines = true;
+               return this;
+       }
+
+       /**
+        * Pipe output to the specified writer.
+        * The method can be called multiple times to write to multiple writers.
+        *
+        * @param w The writer to pipe to.
+        * @param close Close the writer afterwards.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder pipeTo(Writer w, boolean close) {
+               this.outWriters.add(w, close);
+               return this;
+       }
+
+       /**
+        * Pipe output to the specified writer, but don't close the writer.
+        *
+        * @param w The writer to pipe to.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder pipeTo(Writer w) {
+               return pipeTo(w, false);
+       }
+
+       /**
+        * Pipe output to the specified writer, including the command and 
return code.
+        * The method can be called multiple times to write to multiple writers.
+        *
+        * @param w The writer to pipe to.
+        * @param close Close the writer afterwards.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder logTo(Writer w, boolean close) {
+               this.logWriters.add(w, close);
+               this.outWriters.add(w, close);
+               return this;
+       }
+
+       /**
+        * Pipe output to the specified writer, including the command and 
return code.
+        * The method can be called multiple times to write to multiple writers.
+        * Don't close the writer afterwards.
+        *
+        * @param w The writer to pipe to.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder logTo(Writer w) {
+               return logTo(w, false);
+       }
+
+       /**
+        * Pipe output to the specified writer, including the command and 
return code.
+        * The method can be called multiple times to write to multiple writers.
+        *
+        * @param level The log level.
+        * @param logger The logger to log to.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder logTo(final Level level, final Logger logger) {
+               if (logger.isLoggable(level)) {
+                       logTo(new StringWriter() {
+                               private boolean isClosed;  // Prevents messages 
from being written twice.
+                               @Override /* Writer */
+                               public void close() {
+                                       if (! isClosed)
+                                               logger.log(level, 
this.toString());
+                                       isClosed = true;
+                               }
+                       }, true);
+               }
+               return this;
+       }
+
+       /**
+        * Line processor to use to process/convert lines of output returned by 
the process.
+        *
+        * @param lp The new line processor.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder lp(LineProcessor lp) {
+               this.lp = lp;
+               return this;
+       }
+
+       /**
+        * Append the specified environment variables to the process.
+        *
+        * @param env The new set of environment variables.
+        * @return This object (for method chaining).
+        */
+       @SuppressWarnings({"rawtypes", "unchecked"})
+       public ProcBuilder env(Map env) {
+               if (env != null)
+                       for (Map.Entry e : (Set<Map.Entry>)env.entrySet())
+                               environment(e.getKey().toString(), e.getValue() 
== null ? null : e.getValue().toString());
+               return this;
+       }
+
+       /**
+        * Append the specified environment variable.
+        *
+        * @param key The environment variable name.
+        * @param val The environment variable value.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder environment(String key, String val) {
+               pb.environment().put(key, val);
+               return this;
+       }
+
+       /**
+        * Sets the directory where the command will be executed.
+        *
+        * @param directory The directory.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder directory(File directory) {
+               pb.directory(directory);
+               return this;
+       }
+
+       /**
+        * Sets the maximum allowed return code on the process call.
+        * If the return code exceeds this value, an IOException is returned on 
the {@link #run()} command.
+        * The default value is '0'.
+        *
+        * @param maxExitStatus The maximum exit status.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder maxExitStatus(int maxExitStatus) {
+               this.maxExitStatus = maxExitStatus;
+               return this;
+       }
+
+       /**
+        * Run this command and pipes the output to the specified writer or 
output stream.
+        *
+        * @return The exit code from the process.
+        * @throws IOException
+        * @throws InterruptedException
+        */
+       public int run() throws IOException, InterruptedException {
+               if (pb.command().size() == 0)
+                       throw new IOException("No command specified in 
ProcBuilder.");
+               try {
+                       logWriters.append(divider).append('\n').flush();
+                       logWriters.append(StringUtils.join(pb.command(), " 
")).append('\n').flush();
+                       p = pb.start();
+                       IOPipe.create(p.getInputStream(), 
outWriters).lineProcessor(lp).byLines(byLines).run();
+                       int rc = p.waitFor();
+                       logWriters.append("Exit: 
").append(String.valueOf(p.exitValue())).append('\n').flush();
+                       if (rc > maxExitStatus)
+                               throw new IOException("Return code "+rc+" from 
command " + StringUtils.join(pb.command(), " "));
+                       return rc;
+               } finally {
+                       close();
+               }
+       }
+
+       /**
+        * Run this command and returns the output as a simple string.
+        *
+        * @return The output from the command.
+        * @throws IOException
+        * @throws InterruptedException
+        */
+       public String getOutput() throws IOException, InterruptedException {
+               StringWriter sw = new StringWriter();
+               pipeTo(sw).run();
+               return sw.toString();
+       }
+
+       /**
+        * Returns the output from this process as a {@link Scanner}.
+        *
+        * @return The output from the process as a Scanner object.
+        * @throws IOException
+        * @throws InterruptedException
+        */
+       public Scanner getScanner() throws IOException, InterruptedException {
+               StringWriter sw = new StringWriter();
+               pipeTo(sw, true);
+               run();
+               return new Scanner(sw.toString());
+       }
+
+       /**
+        * Destroys the underlying process.
+        * This method is only needed if the {@link #getScanner()} method was 
used.
+        */
+       private void close() {
+               IOUtils.closeQuietly(logWriters, outWriters);
+               if (p != null)
+                       p.destroy();
+       }
+
+       /**
+        * Specifies interface for defining OS-specific commands.
+        */
+       public abstract static class Matcher {
+               abstract boolean matches();
+       }
+
+       private static String OS = System.getProperty("os.name").toLowerCase();
+
+       /** Operating system matcher: Any operating system. */
+       public final static Matcher ANY = new Matcher() {
+               @Override boolean matches() {
+                       return true;
+               }
+       };
+
+       /** Operating system matcher: Any Windows system. */
+       public final static Matcher WINDOWS = new Matcher() {
+               @Override boolean matches() {
+                       return OS.indexOf("win") >= 0;
+               }
+       };
+
+       /** Operating system matcher: Any Mac system. */
+       public final static Matcher MAC = new Matcher() {
+               @Override boolean matches() {
+                       return OS.indexOf("mac") >= 0;
+               }
+       };
+
+       /** Operating system matcher: Any Unix or Linux system. */
+       public final static Matcher UNIX = new Matcher() {
+               @Override boolean matches() {
+                       return OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 
|| OS.indexOf("aix") > 0;
+               }
+       };
+
+       private static List<String> toList(Object...args) {
+               List<String> l = new LinkedList<String>();
+               for (Object o : args) {
+                       if (o.getClass().isArray())
+                               for (int i = 0; i < Array.getLength(o); i++)
+                                       l.add(Array.get(o, i).toString());
+                       else if (o instanceof Collection)
+                               for (Object o2 : (Collection<?>)o)
+                                       l.add(o2.toString());
+                       else
+                               l.add(o.toString());
+               }
+               return l;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/utils/ZipFileList.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/ZipFileList.java 
b/juneau-core/src/main/java/org/apache/juneau/utils/ZipFileList.java
new file mode 100644
index 0000000..84eb222
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/ZipFileList.java
@@ -0,0 +1,140 @@
+/***************************************************************************************************************************
+ * 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.utils;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+
+/**
+ * Utility class for representing the contents of a zip file as a list of 
entries
+ * whose contents don't resolve until serialize time.
+ * <p>
+ * Generally associated with <code>RestServlets</code> using the 
<code>responseHandlers</code>
+ *     annotation so that REST methods can easily create ZIP file responses by 
simply returning instances
+ *     of this class.
+ */
+@SuppressWarnings("serial")
+public class ZipFileList extends LinkedList<ZipFileList.ZipFileEntry> {
+
+       /**
+        * The name of the zip file.
+        */
+       public final String fileName;
+
+       /**
+        * Constructor.
+        *
+        * @param fileName The file name of the zip file to create.
+        */
+       public ZipFileList(String fileName) {
+               this.fileName = fileName;
+       }
+
+       /**
+        * Add an entry to this list.
+        *
+        * @param e The zip file entry.
+        * @return This object (for method chaining).
+        */
+       public ZipFileList append(ZipFileEntry e) {
+               add(e);
+               return this;
+       }
+
+       /**
+        * Interface for ZipFileList entries.
+        */
+       public static interface ZipFileEntry {
+               /**
+                * Write this entry to the specified output stream.
+                *
+                * @param zos The output stream to write to.
+                * @throws IOException
+                */
+               void write(ZipOutputStream zos) throws IOException;
+       }
+
+       /**
+        * ZipFileList entry for File entry types.
+        */
+       public static class FileEntry implements ZipFileEntry {
+
+               /** The root file to base the entry paths on. */
+               protected File root;
+
+               /** The file being zipped. */
+               protected File file;
+
+               /**
+                * Constructor.
+                *
+                * @param root The root file that represents the base path.
+                * @param file The file to add to the zip file.
+                */
+               public FileEntry(File root, File file) {
+                       this.root = root;
+                       this.file = file;
+               }
+
+               /**
+                * Constructor.
+                *
+                * @param file The file to add to the zip file.
+                */
+               public FileEntry(File file) {
+                       this.file = file;
+                       this.root = (file.isDirectory() ? file : 
file.getParentFile());
+               }
+
+               @Override /* ZipFileEntry */
+               public void write(ZipOutputStream zos) throws IOException {
+                       addFile(zos, file);
+               }
+
+               /**
+                * Subclasses can override this method to customize which files 
get added to a zip file.
+                *
+                * @param f The file being added to the zip file.
+                * @return Always returns <jk>true</jk>.
+                */
+               public boolean doAdd(File f) {
+                       return true;
+               }
+
+               /**
+                * Adds the specified file to the specified output stream.
+                *
+                * @param zos The output stream.
+                * @param f The file to add.
+                * @throws IOException
+                */
+               protected void addFile(ZipOutputStream zos, File f) throws 
IOException {
+                       if (doAdd(f)) {
+                               if (f.isDirectory()) {
+                                       File[] fileList = f.listFiles();
+                                       if (fileList == null)
+                                               throw new 
IOException(f.toString());
+                                       for (File fc : fileList)
+                                               addFile(zos, fc);
+                               } else if (f.canRead()) {
+                                       String path = 
f.getAbsolutePath().substring(root.getAbsolutePath().length() + 
1).replace('\\', '/');
+                                       ZipEntry e = new ZipEntry(path);
+                                       e.setSize(f.length());
+                                       zos.putNextEntry(e);
+                                       IOPipe.create(new FileInputStream(f), 
zos).run();
+                               }
+                       }
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/utils/package.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/package.html 
b/juneau-core/src/main/java/org/apache/juneau/utils/package.html
new file mode 100644
index 0000000..c3d6a65
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/package.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<!--
+/***************************************************************************************************************************
+ * 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.
+ *
+ 
***************************************************************************************************************************/
+ -->
+<html>
+<head>
+       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+       <style type="text/css">
+               /* For viewing in Page Designer */
+               @IMPORT url("../../../../../../javadoc.css");
+
+               /* For viewing in REST interface */
+               @IMPORT url("../htdocs/javadoc.css");
+               body { 
+                       margin: 20px; 
+               }       
+       </style>
+       <script>
+               /* Replace all @code and @link tags. */ 
+               window.onload = function() {
+                       document.body.innerHTML = 
document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>');
+                       document.body.innerHTML = 
document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, 
'<code>$3</code>');
+               }
+       </script>
+</head>
+<body>
+<p>Utility classes</p>
+
+<script>
+       function toggle(x) {
+               var div = x.nextSibling;
+               while (div != null && div.nodeType != 1)
+                       div = div.nextSibling;
+               if (div != null) {
+                       var d = div.style.display;
+                       if (d == 'block' || d == '') {
+                               div.style.display = 'none';
+                               x.className += " closed";
+                       } else {
+                               div.style.display = 'block';
+                               x.className = 
x.className.replace(/(?:^|\s)closed(?!\S)/g , '' );
+                       }
+               }
+       }
+</script>
+
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/Namespace.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/Namespace.java 
b/juneau-core/src/main/java/org/apache/juneau/xml/Namespace.java
new file mode 100644
index 0000000..6b361ce
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/Namespace.java
@@ -0,0 +1,89 @@
+/***************************************************************************************************************************
+ * 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.xml;
+
+import org.apache.juneau.annotation.*;
+
+/**
+ * Represents a simple namespace mapping between a simple name and URI.
+ * <p>
+ *     In general, the simple name will be used as the XML prefix mapping 
unless
+ *     there are conflicts or prefix remappings in the serializer.
+ *
+ * @author James Bognar ([email protected])
+ */
+@Bean(sort=true)
+public final class Namespace implements Comparable<Namespace> {
+       final String name, uri;
+       private final int hashCode;
+
+       /**
+        * Constructor.
+        * <p>
+        *      Use this constructor when the long name and short name are the 
same value.
+        *
+        * @param name The long and short name of this schema.
+        * @param uri The URI of this schema.
+        */
+       @BeanConstructor(properties={"name","uri"})
+       public Namespace(String name, String uri) {
+               this.name = name;
+               this.uri = uri;
+               this.hashCode = (name == null ? 0 : name.hashCode()) + 
uri.hashCode();
+       }
+
+       /**
+        * Returns the namespace name.
+        *
+        * @return The namespace name.
+        */
+       public String getName() {
+               return name;
+       }
+
+       /**
+        * Returns the namespace URI.
+        *
+        * @return The namespace URI.
+        */
+       public String getUri() {
+               return uri;
+       }
+
+       @Override /* Comparable */
+       public int compareTo(Namespace o) {
+               int i = name.compareTo(o.name);
+               if (i == 0)
+                       i = uri.compareTo(o.uri);
+               return i;
+       }
+
+       /**
+        * For performance reasons, equality is always based on identity, since
+        * the {@link NamespaceFactory} class ensures no duplicate name+uri 
pairs.
+        */
+       @Override /* Object */
+       public boolean equals(Object o) {
+               return this == o;
+       }
+
+       @Override /* Object */
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return "{name:'"+name+"',uri:'"+uri+"'}";
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/NamespaceFactory.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/xml/NamespaceFactory.java 
b/juneau-core/src/main/java/org/apache/juneau/xml/NamespaceFactory.java
new file mode 100644
index 0000000..ba89619
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/NamespaceFactory.java
@@ -0,0 +1,130 @@
+/***************************************************************************************************************************
+ * 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.xml;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * Factory class for getting unique instances of {@link Namespace} objects.
+ * <p>
+ *     For performance reasons, {@link Namespace} objects are stored in {@link 
IdentityList IdentityLists}.
+ *     For this to work property, namespaces with the same name and URI must 
only be represented by a single
+ *     {@link Namespace} instance.
+ *     This factory class ensures this identity uniqueness.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class NamespaceFactory {
+
+       private static ConcurrentHashMap<String,Namespace> cache = new 
ConcurrentHashMap<String,Namespace>();
+
+       /**
+        * Get the {@link Namespace} with the specified name and URI, and 
create a new one
+        *      if this is the first time it's been encountered.
+        *
+        * @param name The namespace name.  See {@link Namespace#getName()}.
+        * @param uri The namespace URI.  See {@link Namespace#getUri()}.
+        * @return The namespace object.
+        */
+       public static Namespace get(String name, String uri) {
+               String key = name + "+" + uri;
+               Namespace n = cache.get(key);
+               if (n == null) {
+                       n = new Namespace(name, uri);
+                       Namespace n2 = cache.putIfAbsent(key, n);
+                       return (n2 == null ? n : n2);
+               }
+               return n;
+       }
+
+       /**
+        * Converts the specified object into a {@link Namespace} object.
+        * <p>
+        *      Can be any of following types:
+        * <ul class='spaced-list'>
+        *      <li>A {@link Namespace} object
+        *      <li>A JSON string containing a single key/value pair indicating 
the name/URI mapping.
+        *      <li>A <code>Map</code> containing a single key/value pair 
indicating the name/URI mapping.
+        * </ul>
+        *
+        * @param o The input.
+        * @return The namespace object, or <jk>null</jk> if the input was 
<jk>null</jk> or an empty JSON object.
+        */
+       @SuppressWarnings("rawtypes")
+       public static Namespace parseNamespace(Object o) {
+               if (o == null)
+                       return null;
+               if (o instanceof Namespace)
+                       return (Namespace)o;
+               try {
+                       Map<?,?> m = (o instanceof Map ? (Map)o : new 
ObjectMap(o.toString()));
+                       if (m.size() == 0)
+                               return null;
+                       if (m.size() > 1)
+                               throw new RuntimeException("Too many namespaces 
specified.  Only one allowed. '"+o+"'");
+                       Map.Entry<?,?> e = m.entrySet().iterator().next();
+                       return get(e.getKey().toString(), 
e.getValue().toString());
+               } catch (ParseException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       /**
+        * Converts the specified object into an array of {@link Namespace} 
object.
+        * <p>
+        *      Can be any of following types:
+        * <ul class='spaced-list'>
+        *      <li>A {@link Namespace} array
+        *      <li>A JSON string with key/value pairs indicating name/URI 
pairs.
+        *      <li>A <code>Map</code> with key/value pairs indicating name/URI 
pairs.
+        *      <li>A <code>Collection</code> containing any of object that can 
be passed to {@link #parseNamespace(Object)}.
+        * </ul>
+        *
+        * @param o The input.
+        * @return The namespace objects, or <jk>null</jk> if the input was 
<jk>null</jk> or an empty JSON object.
+        */
+       @SuppressWarnings("rawtypes")
+       public static Namespace[] parseNamespaces(Object o) {
+               try {
+                       if (o instanceof Namespace[])
+                               return (Namespace[])o;
+
+                       if (o instanceof CharSequence)
+                               o = new ObjectMap(o.toString());
+
+                       Namespace[] n;
+                       int i = 0;
+                       if (o instanceof Collection) {
+                               Collection c = (Collection)o;
+                               n = new Namespace[c.size()];
+                               for (Object o2 : c)
+                                       n[i++] = parseNamespace(o2);
+                       } else if (o instanceof Map) {
+                               Map<?,?> m = (Map<?,?>)o;
+                               n = new Namespace[m.size()];
+                               for (Map.Entry e : m.entrySet())
+                                       n[i++] = get(e.getKey().toString(), 
e.getValue().toString());
+                       } else {
+                               throw new RuntimeException("Invalid type passed 
to NamespaceFactory.listFromObject: '"+o+"'");
+                       }
+                       return n;
+               } catch (ParseException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanMeta.java 
b/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanMeta.java
new file mode 100644
index 0000000..d6063f7
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanMeta.java
@@ -0,0 +1,129 @@
+/***************************************************************************************************************************
+ * 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.xml;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.xml.annotation.*;
+
+/**
+ * Metadata on beans specific to the XML serializers and parsers pulled from 
the {@link Xml @Xml} annotation on the class.
+ *
+ * @author James Bognar ([email protected])
+ * @param <T> The bean class type.
+ */
+public class XmlBeanMeta<T> {
+
+       // XML related fields
+       private final Map<String,BeanPropertyMeta<T>> xmlAttrs;                 
       // Map of bean properties that are represented as XML attributes.
+       private final BeanPropertyMeta<T> xmlContent;                           
       // Bean property that is represented as XML content within the bean 
element.
+       private final XmlContentHandler<T> xmlContentHandler;                   
       // Class used to convert bean to XML content.
+       private final Map<String,BeanPropertyMeta<T>> childElementProperties;   
       // Properties defined with @Xml.childName annotation.
+       private final BeanMeta<T> beanMeta;
+
+       /**
+        * Constructor.
+        *
+        * @param beanMeta The metadata on the bean that this metadata applies 
to.
+        * @param pNames Only look at these property names.  If <jk>null</jk>, 
apply to all bean properties.
+        */
+       public XmlBeanMeta(BeanMeta<T> beanMeta, String[] pNames) {
+               this.beanMeta = beanMeta;
+               Class<T> c = beanMeta.getClassMeta().getInnerClass();
+
+               Map<String,BeanPropertyMeta<T>> tXmlAttrs = new 
LinkedHashMap<String,BeanPropertyMeta<T>>();
+               BeanPropertyMeta<T> tXmlContent = null;
+               XmlContentHandler<T> tXmlContentHandler = null;
+               Map<String,BeanPropertyMeta<T>> tChildElementProperties = new 
LinkedHashMap<String,BeanPropertyMeta<T>>();
+
+               for (BeanPropertyMeta<T> p : beanMeta.getPropertyMetas(pNames)) 
{
+                       XmlFormat xf = p.getXmlMeta().getXmlFormat();
+                       if (xf == XmlFormat.ATTR)
+                               tXmlAttrs.put(p.getName(), p);
+                       else if (xf == XmlFormat.CONTENT) {
+                               if (tXmlContent != null)
+                                       throw new BeanRuntimeException(c, 
"Multiple instances of CONTENT properties defined on class.  Only one property 
can be designated as such.");
+                               tXmlContent = p;
+                               tXmlContentHandler = 
p.getXmlMeta().getXmlContentHandler();
+                       }
+                       // Look for any properties that are collections with 
@Xml.childName specified.
+                       String n = p.getXmlMeta().getChildName();
+                       if (n != null) {
+                               if (tChildElementProperties.containsKey(n))
+                                       throw new BeanRuntimeException(c, 
"Multiple properties found with the name ''{0}''.", n);
+                               tChildElementProperties.put(n, p);
+                       }
+               }
+
+               xmlAttrs = Collections.unmodifiableMap(tXmlAttrs);
+               xmlContent = tXmlContent;
+               xmlContentHandler = tXmlContentHandler;
+               childElementProperties = (tChildElementProperties.isEmpty() ? 
null : Collections.unmodifiableMap(tChildElementProperties));
+       }
+
+       /**
+        * Returns the list of properties annotated with an {@link 
Xml#format()} of {@link XmlFormat#ATTR}.
+        * In other words, the list of properties that should be rendered as 
XML attributes instead of child elements.
+        *
+        * @return Metadata on the XML attribute properties of the bean.
+        */
+       protected Map<String,BeanPropertyMeta<T>> getXmlAttrProperties() {
+               return xmlAttrs;
+       }
+
+       /**
+        * Returns the bean property annotated with an {@link Xml#format()} 
value of {@link XmlFormat#CONTENT}
+        *
+        * @return The bean property, or <jk>null</jk> if annotation is not 
specified.
+        */
+       protected BeanPropertyMeta<T> getXmlContentProperty() {
+               return xmlContent;
+       }
+
+       /**
+        * Return the XML content handler for this bean.
+        *
+        * @return The XML content handler for this bean, or <jk>null</jk> if 
no content handler is defined.
+        */
+       protected XmlContentHandler<T> getXmlContentHandler() {
+               return xmlContentHandler;
+       }
+
+       /**
+        * Returns the child element properties for this bean.
+        * See {@link Xml#childName()}
+        *
+        * @return The child element properties for this bean, or <jk>null</jk> 
if no child element properties are defined.
+        */
+       protected Map<String,BeanPropertyMeta<T>> getChildElementProperties() {
+               return childElementProperties;
+       }
+
+       /**
+        * Returns bean property meta with the specified name.
+        * This is identical to calling {@link 
BeanMeta#getPropertyMeta(String)} except it first retrieves
+        *      the bean property meta based on the child name (e.g. a property 
whose name is "people", but whose child name is "person").
+        *
+        * @param fieldName The bean property name.
+        * @return The property metadata.
+        */
+       protected BeanPropertyMeta<T> getPropertyMeta(String fieldName) {
+               if (childElementProperties != null) {
+                       BeanPropertyMeta<T> bpm = 
childElementProperties.get(fieldName);
+                       if (bpm != null)
+                               return bpm;
+               }
+               return beanMeta.getPropertyMeta(fieldName);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java 
b/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
new file mode 100644
index 0000000..c4c5e67
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
@@ -0,0 +1,163 @@
+/***************************************************************************************************************************
+ * 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.xml;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.xml.annotation.*;
+
+/**
+ * Metadata on bean properties specific to the XML serializers and parsers 
pulled from the {@link Xml @Xml} annotation on the bean property.
+ *
+ * @author James Bognar ([email protected])
+ * @param <T> The bean class.
+ */
+public class XmlBeanPropertyMeta<T> {
+
+       private Namespace namespace = null;
+       private XmlFormat xmlFormat = XmlFormat.NORMAL;
+       private XmlContentHandler<T> xmlContentHandler = null;
+       private String childName;
+       private final BeanPropertyMeta<T> beanPropertyMeta;
+
+       /**
+        * Constructor.
+        *
+        * @param beanPropertyMeta The metadata of the bean property of this 
additional metadata.
+        */
+       public XmlBeanPropertyMeta(BeanPropertyMeta<T> beanPropertyMeta) {
+               this.beanPropertyMeta = beanPropertyMeta;
+
+               if (beanPropertyMeta.getField() != null)
+                       
findXmlInfo(beanPropertyMeta.getField().getAnnotation(Xml.class));
+               if (beanPropertyMeta.getGetter() != null)
+                       
findXmlInfo(beanPropertyMeta.getGetter().getAnnotation(Xml.class));
+               if (beanPropertyMeta.getSetter() != null)
+                       
findXmlInfo(beanPropertyMeta.getSetter().getAnnotation(Xml.class));
+
+               if (namespace == null)
+                       namespace = 
beanPropertyMeta.getBeanMeta().getClassMeta().getXmlMeta().getNamespace();
+
+               if (beanPropertyMeta.isBeanUri() && xmlFormat != 
XmlFormat.ELEMENT)
+                       xmlFormat = XmlFormat.ATTR;
+       }
+
+       /**
+        * Returns the XML namespace associated with this bean property.
+        * <p>
+        *      Namespace is determined in the following order:
+        * <ol>
+        *      <li>{@link Xml#prefix()} annotation defined on bean property 
field.
+        *      <li>{@link Xml#prefix()} annotation defined on bean getter.
+        *      <li>{@link Xml#prefix()} annotation defined on bean setter.
+        *      <li>{@link Xml#prefix()} annotation defined on bean.
+        *      <li>{@link Xml#prefix()} annotation defined on bean package.
+        *      <li>{@link Xml#prefix()} annotation defined on bean 
superclasses.
+        *      <li>{@link Xml#prefix()} annotation defined on bean superclass 
packages.
+        *      <li>{@link Xml#prefix()} annotation defined on bean interfaces.
+        *      <li>{@link Xml#prefix()} annotation defined on bean interface 
packages.
+        * </ol>
+        *
+        * @return The namespace associated with this bean property, or 
<jk>null</jk> if no namespace is
+        *      associated with it.
+        */
+       public Namespace getNamespace() {
+               return namespace;
+       }
+
+       /**
+        * Returns the XML format of this property from the {@link Xml#format} 
annotation on this bean property.
+        *
+        * @return The XML format, or {@link XmlFormat#NORMAL} if annotation 
not specified.
+        */
+       protected XmlFormat getXmlFormat() {
+               return xmlFormat;
+       }
+
+       /**
+        * Returns the XML content handler of this property from the {@link 
Xml#contentHandler} annotation on this bean property.
+        *
+        * @return The XML content handler, or <jk>null</jk> if annotation not 
specified.
+        */
+       protected XmlContentHandler<T> getXmlContentHandler() {
+               return xmlContentHandler;
+       }
+
+       /**
+        * Returns the child element of this property from the {@link 
Xml#childName} annotation on this bean property.
+        *
+        * @return The child element, or <jk>null</jk> if annotation not 
specified.
+        */
+       protected String getChildName() {
+               return childName;
+       }
+
+       /**
+        * Returns the bean property metadata that this metadata belongs to.
+        *
+        * @return The bean property metadata.  Never <jk>null</jk>.
+        */
+       protected BeanPropertyMeta<T> getBeanPropertyMeta() {
+               return beanPropertyMeta;
+       }
+
+       @SuppressWarnings("unchecked")
+       private void findXmlInfo(Xml xml) {
+               if (xml == null)
+                       return;
+               ClassMeta<?> cmProperty = beanPropertyMeta.getClassMeta();
+               ClassMeta<?> cmBean = 
beanPropertyMeta.getBeanMeta().getClassMeta();
+               String name = beanPropertyMeta.getName();
+               if (! xml.name().isEmpty())
+                       throw new BeanRuntimeException(cmBean.getInnerClass(), 
"Annotation error on property ''{0}''.  Found @Xml.name annotation can only be 
specified on types.", name);
+
+               List<Xml> xmls = beanPropertyMeta.findAnnotations(Xml.class);
+               List<XmlSchema> schemas = 
beanPropertyMeta.findAnnotations(XmlSchema.class);
+               namespace = XmlUtils.findNamespace(xmls, schemas);
+
+               if (xmlFormat == XmlFormat.NORMAL)
+                       xmlFormat = xml.format();
+
+               boolean isCollection = cmProperty.isCollection() || 
cmProperty.isArray();
+
+               String cen = xml.childName();
+               if ((! cen.isEmpty()) && (! isCollection))
+                       throw new 
BeanRuntimeException(cmProperty.getInnerClass(), "Annotation error on property 
''{0}''.  @Xml.childName can only be specified on collections and arrays.", 
name);
+
+               if (xmlFormat == XmlFormat.COLLAPSED) {
+                       if (isCollection) {
+                               if (cen.isEmpty())
+                                       cen = 
cmProperty.getXmlMeta().getChildName();
+                               if (cen == null || cen.isEmpty())
+                                       cen = 
cmProperty.getElementType().getXmlMeta().getElementName();
+                               if (cen == null || cen.isEmpty())
+                                       cen = name;
+                       } else {
+                               throw new 
BeanRuntimeException(cmBean.getInnerClass(), "Annotation error on property 
''{0}''.  @Xml.format=COLLAPSED can only be specified on collections and 
arrays.", name);
+                       }
+                       if (cen.isEmpty() && isCollection)
+                               cen = cmProperty.getXmlMeta().getElementName();
+               }
+
+               try {
+                       if (xmlFormat == XmlFormat.CONTENT && 
xml.contentHandler() != XmlContentHandler.NULL.class)
+                               xmlContentHandler = (XmlContentHandler<T>) 
xml.contentHandler().newInstance();
+               } catch (Exception e) {
+                       throw new BeanRuntimeException(cmBean.getInnerClass(), 
"Could not instantiate content handler ''{0}''", 
xml.contentHandler().getName()).initCause(e);
+               }
+
+               if (! cen.isEmpty())
+                       childName = cen;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlClassMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/xml/XmlClassMeta.java 
b/juneau-core/src/main/java/org/apache/juneau/xml/XmlClassMeta.java
new file mode 100644
index 0000000..1e23d9e
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlClassMeta.java
@@ -0,0 +1,118 @@
+/***************************************************************************************************************************
+ * 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.xml;
+
+import java.util.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.xml.annotation.*;
+
+
+/**
+ * Metadata on classes specific to the XML serializers and parsers pulled from 
the {@link Xml @Xml} annotation on the class.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class XmlClassMeta {
+
+       private final Namespace namespace;
+       private final Xml xml;
+       private final XmlFormat format;
+       private final String elementName;
+       private final String childName;
+
+       /**
+        * Constructor.
+        *
+        * @param c The class that this annotation is defined on.
+        */
+       public XmlClassMeta(Class<?> c) {
+               this.namespace = findNamespace(c);
+               this.xml =  ReflectionUtils.getAnnotation(Xml.class, c);
+               if (xml != null) {
+                       this.format = xml.format();
+                       this.elementName = StringUtils.nullIfEmpty(xml.name());
+                       this.childName = 
StringUtils.nullIfEmpty(xml.childName());
+
+               } else {
+                       this.format = XmlFormat.NORMAL;
+                       this.elementName = null;
+                       this.childName = null;
+               }
+       }
+
+       /**
+        * Returns the {@link Xml} annotation defined on the class.
+        *
+        * @return The value of the {@link Xml} annotation defined on the 
class, or <jk>null</jk> if annotation is not specified.
+        */
+       protected Xml getAnnotation() {
+               return xml;
+       }
+
+       /**
+        * Returns the {@link Xml#format()} annotation defined on the class.
+        *
+        * @return The value of the {@link Xml#format()} annotation, or {@link 
XmlFormat#NORMAL} if not specified.
+        */
+       protected XmlFormat getFormat() {
+               return format;
+       }
+
+       /**
+        * Returns the {@link Xml#name()} annotation defined on the class.
+        *
+        * @return The value of the {@link Xml#name()} annotation, or 
<jk>null</jk> if not specified.
+        */
+       protected String getElementName() {
+               return elementName;
+       }
+
+       /**
+        * Returns the {@link Xml#childName()} annotation defined on the class.
+        *
+        * @return The value of the {@link Xml#childName()} annotation, or 
<jk>null</jk> if not specified.
+        */
+       protected String getChildName() {
+               return childName;
+       }
+
+       /**
+        * Returns the XML namespace associated with this class.
+        * <p>
+        *      Namespace is determined in the following order:
+        * <ol>
+        *      <li>{@link Xml#prefix()} annotation defined on class.
+        *      <li>{@link Xml#prefix()} annotation defined on package.
+        *      <li>{@link Xml#prefix()} annotation defined on superclasses.
+        *      <li>{@link Xml#prefix()} annotation defined on superclass 
packages.
+        *      <li>{@link Xml#prefix()} annotation defined on interfaces.
+        *      <li>{@link Xml#prefix()} annotation defined on interface 
packages.
+        * </ol>
+        *
+        * @return The namespace associated with this class, or <jk>null</jk> 
if no namespace is
+        *      associated with it.
+        */
+       protected Namespace getNamespace() {
+               return namespace;
+       }
+
+       private Namespace findNamespace(Class<?> c) {
+               if (c == null)
+                       return null;
+
+               List<Xml> xmls = ReflectionUtils.findAnnotations(Xml.class, c);
+               List<XmlSchema> schemas = 
ReflectionUtils.findAnnotations(XmlSchema.class, c);
+               return XmlUtils.findNamespace(xmls, schemas);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/xml/XmlContentHandler.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/xml/XmlContentHandler.java 
b/juneau-core/src/main/java/org/apache/juneau/xml/XmlContentHandler.java
new file mode 100644
index 0000000..377be5d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/xml/XmlContentHandler.java
@@ -0,0 +1,139 @@
+/***************************************************************************************************************************
+ * 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.xml;
+
+import javax.xml.stream.*;
+
+import org.apache.juneau.dto.atom.*;
+import org.apache.juneau.xml.annotation.*;
+
+/**
+ * Customization class that allows a bean (or parts of a bean) to be 
serialized as XML text or mixed content.
+ * <p>
+ *     For example, the ATOM specification allows text elements (e.g. title, 
subtitle...)
+ *             to be either plain text or XML depending on the value of a 
<xa>type</xa> attribute.
+ *     The behavior of text escaping thus depends on that attribute.
+ *
+ * <p class='bcode'>
+ *     <xt>&lt;feed</xt> 
<xa>xmlns</xa>=<xs>"http://www.w3.org/2005/Atom";</xs><xt>&gt;</xt>
+ *             <xt>&lt;title</xt> <xa>type</xa>=<xs>"html"</xs><xt>&gt;</xt>
+ *                     &amp;lt;p&amp;gt;&amp;lt;i&amp;gt;This is the 
title&amp;lt;/i&amp;gt;&amp;lt;/p&amp;gt;
+ *             <xt>&lt;/title&gt;</xt>
+ *             <xt>&lt;title</xt> <xa>type</xa>=<xs>"xhtml"</xs><xt>&gt;</xt>
+ *                     <xt>&lt;div</xt> 
<xa>xmlns</xa>=<xs>"http://www.w3.org/1999/xhtml";</xs><xt>&gt;</xt>
+ *                             <xt>&lt;p&gt;&lt;i&gt;</xt>This is the 
subtitle<xt>&lt;/i&gt;&lt;/p&gt;</xt>
+ *                     <xt>&lt;/div&gt;</xt>
+ *             <xt>&lt;/title&gt;</xt>
+ *     <xt>&lt;/feed&gt;</xt>
+ * </p>
+ *
+ * <p>
+ *     The ATOM {@link Text} class (the implementation for both the 
<xt>&lt;title&gt;</xt> and <xt>&lt;subtitle&gt;</xt>
+ *             tags shown above) then associates a content handler through the 
{@link Xml#contentHandler()} annotation
+ *             on the bean property containing the text, like so...
+ *
+ * <p class='bcode'>
+ *     <ja>@Xml</ja>(format=<jsf>ATTR</jsf>)
+ *     <jk>public</jk> String getType() {
+ *             <jk>return</jk> <jf>type</jf>;
+ *     }
+ *
+ *     <ja>@Xml</ja>(format=<jsf>CONTENT</jsf>, 
contentHandler=TextContentHandler.<jk>class</jk>)
+ *     <jk>public</jk> String getText() {
+ *             <jk>return</jk> <jf>text</jf>;
+ *     }
+ *
+ *     <jk>public void</jk> setText(String text) {
+ *             <jk>this</jk>.<jf>text</jf> = text;
+ *     }
+ * </p>
+ *
+ * <p>
+ *     The content handler that transforms the output is shown below...
+ *
+ * <p class='bcode'>
+ *     <jk>public static class</jk> TextContentHandler <jk>implements</jk> 
XmlContentHandler&lt;Text&gt; {
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> parse(XMLStreamReader r, Text text) 
<jk>throws</jk> Exception {
+ *                     String type = text.<jf>type</jf>;
+ *                     <jk>if</jk> (type != <jk>null</jk> && 
type.equals(<js>"xhtml"</js>))
+ *                             text.<jf>text</jf> = 
<jsm>decode</jsm>(readXmlContents(r).trim());
+ *                     <jk>else</jk>
+ *                             text.<jf>text</jf> = 
<jsm>decode</jsm>(r.getElementText().trim());
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> serialize(XmlSerializerWriter w, Text 
text) <jk>throws</jk> Exception {
+ *                     String type = text.<jf>type</jf>;
+ *                     String content = text.<jf>text</jf>;
+ *                     <jk>if</jk> (type != <jk>null</jk> && 
type.equals(<js>"xhtml"</js>))
+ *                             w.encodeTextInvalidChars(content);
+ *                     <jk>else</jk>
+ *                             w.encodeText(content);
+ *             }
+ *     }
+ * </p>
+ *
+ * <h6 class='topic'>Notes</h6>
+ * <ul class='spaced-list'>
+ *     <li>The {@link Xml#contentHandler()} annotation can only be specified 
on a bean class, or a bean property
+ *             of format {@link XmlFormat#CONTENT}.
+ * </ul>
+ *
+ *
+ * @author James Bognar ([email protected])
+ * @param <T> The class type of the bean
+ */
+public interface XmlContentHandler<T> {
+
+       /**
+        * Represents <jk>null</jk> on the {@link Xml#contentHandler()} 
annotation.
+        */
+       public static interface NULL extends XmlContentHandler<Object> {}
+
+       /**
+        * Reads XML element content the specified reader and sets the 
appropriate value on the specified bean.
+        * <p>
+        *      When this method is called, the attributes have already been 
parsed and set on the bean.
+        *      Therefore, if the content handling is different based on some 
XML attribute (e.g.
+        *              <code><xa>type</xa>=<xs>"text/xml"</xs></code> vs 
<code><xa>type</xa>=<xs>"text/plain"</xs></code>)
+        *              then that attribute value can be obtained via the set 
bean property.
+        *
+        * @param r The XML stream reader.
+        *      When called, the reader is positioned on the element containing 
the text to read.
+        *      For example, calling <code>r.getElementText()</code> can be 
called immediately
+        *      to return the element text if the element contains only 
characters and whitespace.
+        *      However typically, the stream is going to contain XML elements 
that need to
+        *      be handled special (otherwise you wouldn't need to use an 
<code>XmlContentHandler</code>
+        *      to begin with).
+        * @param bean The bean where the parsed contents are going to be 
placed.
+        *      Subclasses determine how the content maps to values in the bean.
+        *      However, typically the contents map to a single property on the 
bean.
+        * @throws Exception If any problem occurs.  Causes parse to fail.
+        */
+       public void parse(XMLStreamReader r, T bean) throws Exception;
+
+       /**
+        * Writes XML element content from values in the specified bean.
+        *
+        * @param w The XML output writer.
+        *      When called, the XML element/attributes and
+        *              whitespace/indentation (if enabled) have already been 
written to the stream.
+        *      Subclasses must simply write the contents of the element.
+        * @param bean The bean whose values will be converted to XML content.
+        * @throws Exception If any problems occur.  Causes serialize to fail.
+        */
+       public void serialize(XmlWriter w, T bean) throws Exception;
+
+}

Reply via email to