http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java index 3079ccb..aef3f64 100644 --- a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java @@ -203,16 +203,17 @@ public class UonSerializer extends WriterSerializer { * @param eType The expected type of the object if this is a bean property. * @param attrName The bean property name if this is a bean property. <jk>null</jk> if this isn't a bean property being serialized. * @param pMeta The bean property metadata. + * @param plainTextParams <jk>true</jk> if this is a top level parameter key or value and paramFormat is PLAINTEXT. * * @return The same writer passed in. * @throws Exception */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected SerializerWriter serializeAnything(UonSerializerSession session, UonWriter out, Object o, ClassMeta<?> eType, - String attrName, BeanPropertyMeta pMeta) throws Exception { + String attrName, BeanPropertyMeta pMeta, boolean plainTextParams) throws Exception { if (o == null) { - out.appendObject(null, false); + out.appendObject(null, false, plainTextParams); return out; } @@ -247,7 +248,7 @@ public class UonSerializer extends WriterSerializer { // '\0' characters are considered null. if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) - out.appendObject(null, false); + out.appendObject(null, false, plainTextParams); else if (sType.isBoolean()) out.appendBoolean(o); else if (sType.isNumber()) @@ -269,7 +270,7 @@ public class UonSerializer extends WriterSerializer { serializeCollection(session, out, toList(sType.getInnerClass(), o), eType); } else { - out.appendObject(o, false); + out.appendObject(o, false, plainTextParams); } if (! isRecursion) @@ -293,8 +294,8 @@ public class UonSerializer extends WriterSerializer { Map.Entry e = (Map.Entry) mapEntries.next(); Object value = e.getValue(); Object key = session.generalize(e.getKey(), keyType); - out.cr(depth).appendObject(key, false).append('='); - serializeAnything(session, out, value, valueType, (key == null ? null : session.toString(key)), null); + out.cr(depth).appendObject(key, false, false).append('='); + serializeAnything(session, out, value, valueType, (key == null ? null : session.toString(key)), null, false); if (mapEntries.hasNext()) out.append(','); } @@ -329,9 +330,9 @@ public class UonSerializer extends WriterSerializer { if (addComma) out.append(','); - out.cr(depth).appendObject(key, false).append('='); + out.cr(depth).appendObject(key, false, false).append('='); - serializeAnything(session, out, value, cMeta, key, pMeta); + serializeAnything(session, out, value, cMeta, key, pMeta, false); addComma = true; } @@ -356,7 +357,7 @@ public class UonSerializer extends WriterSerializer { for (Iterator i = c.iterator(); i.hasNext();) { out.cr(depth); - serializeAnything(session, out, i.next(), elementType, "<iterator>", null); + serializeAnything(session, out, i.next(), elementType, "<iterator>", null, false); if (i.hasNext()) out.append(','); } @@ -375,12 +376,12 @@ public class UonSerializer extends WriterSerializer { @Override /* Serializer */ public UonSerializerSession createSession(Object output, ObjectMap op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) { - return new UonSerializerSession(ctx, op, output, javaMethod, locale, timeZone, mediaType); + return new UonSerializerSession(ctx, null, op, output, javaMethod, locale, timeZone, mediaType); } @Override /* Serializer */ protected void doSerialize(SerializerSession session, Object o) throws Exception { UonSerializerSession s = (UonSerializerSession)session; - serializeAnything(s, s.getWriter(), o, s.getExpectedRootType(o), "root", null); + serializeAnything(s, s.getWriter(), o, s.getExpectedRootType(o), "root", null, false); } }
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java index 92fbe56..742d0da 100644 --- a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java @@ -38,6 +38,7 @@ public class UonSerializerSession extends SerializerSession { * * @param ctx The context creating this session object. * The context contains all the configuration settings for this object. + * @param encode Override the {@link UonSerializerContext#UON_encodeChars} setting. * @param output The output object. See {@link JsonSerializerSession#getWriter()} for valid class types. * @param op The override properties. * These override any context properties defined in the context. @@ -48,13 +49,13 @@ public class UonSerializerSession extends SerializerSession { * If <jk>null</jk>, then the timezone defined on the context is used. * @param mediaType The session media type (e.g. <js>"application/json"</js>). */ - protected UonSerializerSession(UonSerializerContext ctx, ObjectMap op, Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) { + protected UonSerializerSession(UonSerializerContext ctx, Boolean encode, ObjectMap op, Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) { super(ctx, op, output, javaMethod, locale, timeZone, mediaType); if (op == null || op.isEmpty()) { - encodeChars = ctx.encodeChars; + encodeChars = encode == null ? ctx.encodeChars : encode; addBeanTypeProperties = ctx.addBeanTypeProperties; } else { - encodeChars = op.getBoolean(UON_encodeChars, ctx.encodeChars); + encodeChars = encode == null ? op.getBoolean(UON_encodeChars, ctx.encodeChars) : encode; addBeanTypeProperties = op.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/uon/UonUtils.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonUtils.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonUtils.java new file mode 100644 index 0000000..0f68a77 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonUtils.java @@ -0,0 +1,52 @@ +// *************************************************************************************************************************** +// * 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.uon; + +import org.apache.juneau.internal.*; + +/** + * Utility methods for the UON and UrlEncoding serializers and parsers. + */ +public final class UonUtils { + + private static final AsciiSet needsQuoteChars = new AsciiSet("),=\n\t\r\b\f "); + private static final AsciiSet maybeNeedsQuotesFirstChar = new AsciiSet("),=\n\t\r\b\f tfn+-.#0123456789"); + + /** + * Returns <jk>true</jk> if the specified string needs to be quoted per UON notation. + * <p> + * For example, strings that start with '(' or '@' or look like boolean or numeric values + * need to be quoted. + * + * @param s The string to test. + * @return <jk>true</jk> if the specified string needs to be quoted per UON notation. + */ + public static final boolean needsQuotes(String s) { + char c0 = s.isEmpty() ? 0 : s.charAt(0); + return ( + s.isEmpty() + || c0 == '@' + || c0 == '(' + || needsQuoteChars.contains(s) + || ( + maybeNeedsQuotesFirstChar.contains(c0) + && ( + "true".equals(s) + || "false".equals(s) + || "null".equals(s) + || StringUtils.isNumeric(s) + ) + ) + ); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java index 96f2405..d897e6d 100644 --- a/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java +++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonWriter.java @@ -40,9 +40,7 @@ public final class UonWriter extends SerializerWriter { // Characters that need to be preceeded with an escape character. private static final AsciiSet escapedChars = new AsciiSet("~'"); - private static final AsciiSet needsQuoteChars = new AsciiSet("),=\n\t\r\b\f "); - - private static final AsciiSet maybeNeedsQuotesFirstChar = new AsciiSet("),=\n\t\r\b\f tfn+-.#0123456789"); + private static final AsciiSet noChars = new AsciiSet(""); private static char[] hexArray = "0123456789ABCDEF".toCharArray(); @@ -68,10 +66,11 @@ public final class UonWriter extends SerializerWriter { * * @param o The object being serialized. * @param isTopAttrName If this is a top-level attribute name we're serializing. + * @param plainTextParams This is a top-level name or parameter we're serializing and the parameter format is PLAINTEXT. * @return This object (for method chaining). * @throws IOException Should never happen. */ - public final UonWriter appendObject(Object o, boolean isTopAttrName) throws IOException { + public final UonWriter appendObject(Object o, boolean isTopAttrName, boolean plainTextParams) throws IOException { if (o instanceof Boolean) return appendBoolean(o); @@ -81,26 +80,11 @@ public final class UonWriter extends SerializerWriter { return append("null"); String s = session.toString(o); - char c0 = s.isEmpty() ? 0 : s.charAt(0); - - boolean needsQuotes = - s.isEmpty() - || c0 == '@' - || c0 == '(' - || needsQuoteChars.contains(s) - || ( - maybeNeedsQuotesFirstChar.contains(c0) - && ( - "true".equals(s) - || "false".equals(s) - || "null".equals(s) - || StringUtils.isNumeric(s) - ) - ) - ; + + boolean needsQuotes = (! plainTextParams) && UonUtils.needsQuotes(s); AsciiSet unenc = (isTopAttrName ? unencodedCharsAttrName : unencodedChars); - AsciiSet esc = escapedChars; + AsciiSet esc = plainTextParams ? noChars : escapedChars; if (needsQuotes) append('\''); @@ -193,7 +177,7 @@ public final class UonWriter extends SerializerWriter { } } } - return appendObject(s, false); + return appendObject(s, false, false); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java index 6ecf88d..f20b590 100644 --- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java @@ -415,7 +415,7 @@ public class UrlEncodingParser extends UonParser { } /** - * Parses a single query parameter value into the specified class type. + * Parses a single query parameter or header value into the specified class type. * * @param in The input query string value. * @param type The object type to create. @@ -426,48 +426,28 @@ public class UrlEncodingParser extends UonParser { * @return A new instance of the specified type. * @throws ParseException */ - public <T> T parseParameter(CharSequence in, Type type, Type...args) throws ParseException { + public <T> T parsePart(String in, Type type, Type...args) throws ParseException { if (in == null) return null; - UonParserSession session = createParameterSession(in); - try { - UonReader r = session.getReader(); - return (T)super.parseAnything(session, session.getClassMeta(type, args), r, null, true, null); - } catch (ParseException e) { - throw e; - } catch (Exception e) { - throw new ParseException(session, e); - } finally { - session.close(); - } + return (T)parsePart(in, getBeanContext().getClassMeta(type, args)); } /** - * Parses a single query parameter value into the specified class type. + * Parses a single query parameter or header value into the specified class type. * * @param in The input query string value. * @param type The class type of the object to create. * @return A new instance of the specified type. * @throws ParseException */ - public <T> T parseParameter(CharSequence in, Class<T> type) throws ParseException { + public <T> T parsePart(String in, Class<T> type) throws ParseException { if (in == null) return null; - UonParserSession session = createParameterSession(in); - try { - UonReader r = session.getReader(); - return super.parseAnything(session, session.getClassMeta(type), r, null, true, null); - } catch (ParseException e) { - throw e; - } catch (Exception e) { - throw new ParseException(session, e); - } finally { - session.close(); - } + return parsePart(in, getBeanContext().getClassMeta(type)); } /** - * Same as {@link #parseParameter(CharSequence, Type, Type...)} except the type has already + * Same as {@link #parsePart(String, Type, Type...)} except the type has already * been converted to a {@link ClassMeta} object. * * @param in The input query string value. @@ -475,9 +455,19 @@ public class UrlEncodingParser extends UonParser { * @return A new instance of the specified type. * @throws ParseException */ - public <T> T parseParameter(CharSequence in, ClassMeta<T> type) throws ParseException { + public <T> T parsePart(String in, ClassMeta<T> type) throws ParseException { if (in == null) return null; + if (type.isString() && in.length() > 0) { + // Shortcut - If we're returning a string and the value doesn't start with "'" or is "null", then + // just return the string since it's a plain value. + // This allows us to bypass the creation of a UonParserSession object. + char x = StringUtils.firstNonWhitespaceChar(in); + if (x != '\'' && x != 'n' && in.indexOf('~') == -1) + return (T)in; + if (x == 'n' && "null".equals(in)) + return null; + } UonParserSession session = createParameterSession(in); try { UonReader r = session.getReader(); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java index 222ad7c..eb7ef87 100644 --- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java @@ -246,7 +246,7 @@ public class UrlEncodingSerializer extends UonSerializer { // All other types can't be serialized as key/value pairs, so we create a // mock key/value pair with a "_value" key. out.append("_value="); - super.serializeAnything(session, out, o, null, null, null); + super.serializeAnything(session, out, o, null, null, null, session.plainTextParams()); } session.pop(); @@ -277,6 +277,7 @@ public class UrlEncodingSerializer extends UonSerializer { @SuppressWarnings({ "rawtypes", "unchecked" }) private SerializerWriter serializeMap(UrlEncodingSerializerSession session, UonWriter out, Map m, ClassMeta<?> type) throws Exception { + boolean plainTextParams = session.plainTextParams(); m = session.sort(m); ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); @@ -293,15 +294,15 @@ public class UrlEncodingSerializer extends UonSerializer { while (i.hasNext()) { if (addAmp) out.cr(depth).append('&'); - out.appendObject(key, true).append('='); - super.serializeAnything(session, out, i.next(), null, (key == null ? null : key.toString()), null); + out.appendObject(key, true, plainTextParams).append('='); + super.serializeAnything(session, out, i.next(), null, (key == null ? null : key.toString()), null, plainTextParams); addAmp = true; } } else { if (addAmp) out.cr(depth).append('&'); - out.appendObject(key, true).append('='); - super.serializeAnything(session, out, value, valueType, (key == null ? null : key.toString()), null); + out.appendObject(key, true, plainTextParams).append('='); + super.serializeAnything(session, out, value, valueType, (key == null ? null : key.toString()), null, plainTextParams); addAmp = true; } } @@ -321,7 +322,7 @@ public class UrlEncodingSerializer extends UonSerializer { if (addAmp) out.cr(depth).append('&'); out.append(e.getKey()).append('='); - super.serializeAnything(session, out, e.getValue(), valueType, null, null); + super.serializeAnything(session, out, e.getValue(), valueType, null, null, session.plainTextParams()); addAmp = true; } @@ -331,6 +332,7 @@ public class UrlEncodingSerializer extends UonSerializer { @SuppressWarnings({ "rawtypes" }) private SerializerWriter serializeBeanMap(UrlEncodingSerializerSession session, UonWriter out, BeanMap<?> m, String typeName) throws Exception { int depth = session.getIndent(); + boolean plainTextParams = session.plainTextParams(); boolean addAmp = false; @@ -355,9 +357,9 @@ public class UrlEncodingSerializer extends UonSerializer { if (addAmp) out.cr(depth).append('&'); - out.appendObject(key, true).append('='); + out.appendObject(key, true, plainTextParams).append('='); - super.serializeAnything(session, out, i.next(), cMeta.getElementType(), key, pMeta); + super.serializeAnything(session, out, i.next(), cMeta.getElementType(), key, pMeta, plainTextParams); addAmp = true; } @@ -365,9 +367,9 @@ public class UrlEncodingSerializer extends UonSerializer { if (addAmp) out.cr(depth).append('&'); - out.appendObject(key, true).append('='); + out.appendObject(key, true, plainTextParams).append('='); - super.serializeAnything(session, out, value, cMeta, key, pMeta); + super.serializeAnything(session, out, value, cMeta, key, pMeta, plainTextParams); addAmp = true; } @@ -387,18 +389,29 @@ public class UrlEncodingSerializer extends UonSerializer { * Useful for constructing URL parts. * * @param o The object to serialize. + * @param urlEncode URL-encode the string if necessary. + * If <jk>null</jk>, then uses the value of the {@link UonSerializerContext#UON_encodeChars} setting. + * @param plainTextParams Whether we're using plain-text params. + * If <jk>null</jk>, then uses the value from the {@link UrlEncodingSerializerContext#URLENC_paramFormat} setting. * @return The serialized object. */ - public String serializeUrlPart(Object o) { + public String serializePart(Object o, Boolean urlEncode, Boolean plainTextParams) { try { // Shortcut for simple types. ClassMeta<?> cm = getBeanContext().getClassMetaForObject(o); - if (cm != null) - if (cm.isCharSequence() || cm.isNumber() || cm.isBoolean()) + if (cm != null) { + if (cm.isNumber() || cm.isBoolean()) return o.toString(); + if (cm.isCharSequence()) { + String s = o.toString(); + boolean ptt = (plainTextParams != null ? plainTextParams : ctx.plainTextParams); + if (ptt || ! UonUtils.needsQuotes(s)) + return (urlEncode ? StringUtils.urlEncode(s) : s); + } + } StringWriter w = new StringWriter(); - UonSerializerSession s = createSession(w, null, null, null, null, MediaType.UON); + UonSerializerSession s = new UrlEncodingSerializerSession(ctx, urlEncode, null, w, null, null, null, MediaType.UON); super.doSerialize(s, o); return w.toString(); } catch (Exception e) { @@ -413,7 +426,7 @@ public class UrlEncodingSerializer extends UonSerializer { @Override /* Serializer */ public UrlEncodingSerializerSession createSession(Object output, ObjectMap op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) { - return new UrlEncodingSerializerSession(ctx, op, output, javaMethod, locale, timeZone, mediaType); + return new UrlEncodingSerializerSession(ctx, null, op, output, javaMethod, locale, timeZone, mediaType); } @Override /* Serializer */ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java index fbca471..7f8b00a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java +++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerBuilder.java @@ -97,6 +97,48 @@ public class UrlEncodingSerializerBuilder extends UonSerializerBuilder { return property(UrlEncodingContext.URLENC_expandedParams, value); } + /** + * <b>Configuration property:</b> Format to use for top-level query names and simple parameters. + * <p> + * <ul> + * <li><b>Name:</b> <js>"UrlEncodingSerializer.paramFormat"</js> + * <li><b>Data type:</b> <code>String</code> + * <li><b>Default:</b> <js>"UON"</js> + * <li><b>Session-overridable:</b> <jk>true</jk> + * </ul> + * <p> + * Specifies the format to use for URL GET parameter keys and values. + * <p> + * The possible values are: + * <ul> + * <li><js>"UON"</js> (default) - Use UON notation for values. + * <br>String values such as <js>"(foo='bar')"</js> will end up being quoted and escaped to <js>"'(foo=bar~'baz~')'"</js>. + * <br>Similarly, boolean and numeric values will also end up quoted. + * <li><js>"PLAINTEXT"</js> (default) - Serialize as plain text. + * <br>Strings will never be quoted or escaped. + * <br>Note that this can cause errors during parsing if you're using the URL-encoding parser to parse + * the results since UON constructs won't be differentiatable. + * <br>However, this is not an issue if you're simply creating queries or form posts against 3rd-party interfaces. + * </ul> + * + * @param paramFormat The new value for this property. + * @return This object (for method chaining). + * @see UrlEncodingSerializerContext#URLENC_paramFormat + */ + public UrlEncodingSerializerBuilder paramFormat(String paramFormat) { + return property(UrlEncodingSerializerContext.URLENC_paramFormat, paramFormat); + } + + /** + * Shortcut for calling <code>paramFormat(<js>"PLAINTEXT"</js>)</code>. + * + * @return This object (for method chaining). + * @see UrlEncodingSerializerContext#URLENC_paramFormat + */ + public UrlEncodingSerializerBuilder plainTextParams() { + return paramFormat("PLAINTEXT"); + } + @Override /* UonSerializerBuilder */ public UrlEncodingSerializerBuilder encodeChars(boolean value) { super.encodeChars(value); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java index 7c546ec..1894b0a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java +++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java @@ -25,8 +25,36 @@ import org.apache.juneau.uon.*; */ public class UrlEncodingSerializerContext extends UonSerializerContext { + /** + * <b>Configuration property:</b> Format to use for top-level query names and simple parameters. + * <p> + * <ul> + * <li><b>Name:</b> <js>"UrlEncodingSerializer.paramFormat"</js> + * <li><b>Data type:</b> <code>String</code> + * <li><b>Default:</b> <js>"UON"</js> + * <li><b>Session-overridable:</b> <jk>true</jk> + * </ul> + * <p> + * Specifies the format to use for URL GET parameter keys and values. + * <p> + * The possible values are: + * <ul> + * <li><js>"UON"</js> (default) - Use UON notation for values. + * <br>String values such as <js>"(foo='bar')"</js> will end up being quoted and escaped to <js>"'(foo=bar~'baz~')'"</js>. + * <br>Similarly, boolean and numeric values will also end up quoted. + * <li><js>"PLAINTEXT"</js> (default) - Serialize as plain text. + * <br>Strings will never be quoted or escaped. + * <br>Note that this can cause errors during parsing if you're using the URL-encoding parser to parse + * the results since UON constructs won't be differentiatable. + * <br>However, this is not an issue if you're simply creating queries or form posts against 3rd-party interfaces. + * </ul> + */ + public static final String URLENC_paramFormat = "UrlEncodingSerializer.paramFormat"; + + final boolean - expandedParams; + expandedParams, + plainTextParams; /** * Constructor. @@ -38,6 +66,7 @@ public class UrlEncodingSerializerContext extends UonSerializerContext { public UrlEncodingSerializerContext(PropertyStore ps) { super(ps); this.expandedParams = ps.getProperty(UrlEncodingContext.URLENC_expandedParams, boolean.class, false); + this.plainTextParams = ps.getProperty(UrlEncodingSerializerContext.URLENC_paramFormat, String.class, "UON").equals("PLAINTEXT"); } @Override /* Context */ @@ -45,6 +74,7 @@ public class UrlEncodingSerializerContext extends UonSerializerContext { return super.asMap() .append("UrlEncodingSerializerContext", new ObjectMap() .append("expandedParams", expandedParams) + .append("plainTextParams", plainTextParams) ); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java index 30b5b5c..3f3c47e 100644 --- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java @@ -26,13 +26,14 @@ import org.apache.juneau.uon.*; */ public class UrlEncodingSerializerSession extends UonSerializerSession { - private final boolean expandedParams; + private final boolean expandedParams, plainTextParams; /** * Create a new session using properties specified in the context. * * @param ctx The context creating this session object. * The context contains all the configuration settings for this object. + * @param encode Overrides the {@link UonSerializerContext#UON_encodeChars} setting. * @param output The output object. See {@link JsonSerializerSession#getWriter()} for valid class types. * @param op The override properties. * These override any context properties defined in the context. @@ -43,12 +44,14 @@ public class UrlEncodingSerializerSession extends UonSerializerSession { * If <jk>null</jk>, then the timezone defined on the context is used. * @param mediaType The session media type (e.g. <js>"application/json"</js>). */ - public UrlEncodingSerializerSession(UrlEncodingSerializerContext ctx, ObjectMap op, Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) { - super(ctx, op, output, javaMethod, locale, timeZone, mediaType); + public UrlEncodingSerializerSession(UrlEncodingSerializerContext ctx, Boolean encode, ObjectMap op, Object output, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) { + super(ctx, encode, op, output, javaMethod, locale, timeZone, mediaType); if (op == null || op.isEmpty()) { expandedParams = ctx.expandedParams; + plainTextParams = ctx.plainTextParams; } else { expandedParams = op.getBoolean(UrlEncodingContext.URLENC_expandedParams, false); + plainTextParams = op.getString(UrlEncodingSerializerContext.URLENC_paramFormat, "UON").equals("PLAINTEXT"); } } @@ -85,4 +88,12 @@ public class UrlEncodingSerializerSession extends UonSerializerSession { } return false; } + + /** + * Returns <jk>true</jk> if the {@link UrlEncodingSerializerContext#URLENC_paramFormat} is <js>"PLAINTEXT"</js>. + * @return <jk>true</jk> if the {@link UrlEncodingSerializerContext#URLENC_paramFormat} is <js>"PLAINTEXT"</js>. + */ + protected boolean plainTextParams() { + return plainTextParams; + } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/utils/AList.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/AList.java b/juneau-core/src/main/java/org/apache/juneau/utils/AList.java index f9b7afa..7d5dc64 100644 --- a/juneau-core/src/main/java/org/apache/juneau/utils/AList.java +++ b/juneau-core/src/main/java/org/apache/juneau/utils/AList.java @@ -38,4 +38,15 @@ public final class AList<T> extends LinkedList<T> { add(t); return this; } + + /** + * Adds multiple entries to this list. + * + * @param t The entries to add to this list. + * @return This object (for method chaining). + */ + public AList<T> appendAll(T...t) { + addAll(Arrays.asList(t)); + return this; + } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/java/org/apache/juneau/utils/ASet.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/ASet.java b/juneau-core/src/main/java/org/apache/juneau/utils/ASet.java index a743691..86dc8ca 100644 --- a/juneau-core/src/main/java/org/apache/juneau/utils/ASet.java +++ b/juneau-core/src/main/java/org/apache/juneau/utils/ASet.java @@ -38,4 +38,15 @@ public final class ASet<T> extends LinkedHashSet<T> { add(t); return this; } + + /** + * Adds multiple entries to this set. + * + * @param t The entries to add to this set. + * @return This object (for method chaining). + */ + public ASet<T> appendAll(T...t) { + addAll(Arrays.asList(t)); + return this; + } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-core/src/main/javadoc/overview.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html index f59f283..6f8bc74 100644 --- a/juneau-core/src/main/javadoc/overview.html +++ b/juneau-core/src/main/javadoc/overview.html @@ -99,6 +99,9 @@ <li><p><a class='doclink' href='#Server'>Juneau Server (org.apache.juneau.rest)</a></p> <li><p><a class='doclink' href='#Client'>Juneau Client (org.apache.juneau.rest.client)</a></p> <li><p><a class='doclink' href='#Remoteable'>Remoteable services (org.apache.juneau.rest.remoteable)</a></p> + <ol> + <li><p><a class='doclink' href='#Remoteable.3rdParty'>Interface proxies against 3rd-party REST interfaces</a></p> + </ol> <li><p><a class='doclink' href='#Microservices'>Juneau Microservices (org.apache.juneau.microservice)</a></p> <li><p><a class='doclink' href='#Samples'>Samples</a></p> <ol> @@ -2247,14 +2250,18 @@ <p> Juneau provides the capability of calling methods on POJOs on a server through client-side proxy interfaces. It offers a number of advantages over other similar remote proxy interfaces, such as being much simpler to - use and allowing much more flexibility. + define and use, and allowing much more flexibility in the types of objects serialized. </p> <p> The remote proxy interface API allows you to invoke server-side POJO methods on the client side using REST as the communications protocol: </p> <p class='bcode'> + + <jc>// Create a client with basic JSON support.</jc> + RestClient client = <jk>new</jk> RestClientBuilder().rootUrl(<js>"http://localhost/remoteable"</js>).build(); + <jc>// Get an interface proxy.</jc> - IAddressBook ab = restClient.getRemoteableProxy(IAddressBook.<jk>class</jk>); + IAddressBook ab = client.getRemoteableProxy(IAddressBook.<jk>class</jk>); <jc>// Invoke a method on the server side and get the returned result.</jc> Person p = ab.createPerson( @@ -2266,9 +2273,53 @@ ); </p> <p> + Under the covers, this method call gets converted to a REST POST. + </p> + <p class='bcode'> + HTTP POST http://localhost/remoteable/org.apache.juneau.examples.rest.IAddressBook/createPerson + Accept: application/json + Content-Type: application/json + + [ + { + "name":"John Smith", + "birthDate":"Aug 1, 1999", + "addresses":[ + { + "street":"My street", + "city":"My city", + "state":"My state", + "zip":12345, + "isCurrent":true + } + ] + } + ] + </p> + <p> + Note that the body of the request is an array. This array contains the serialized arguments of the method. + The object returned by the method is then serialized as the body of the response. + </p> + <p> + To define a remoteable interface, simply add the {@link org.apache.juneau.remoteable.Remoteable @Remoteable} annotation + to your interface class. + </p> + <p class='bcode'> + <ja>@Remoteable</ja> + <jk>public interface</jk> IAddressBook {...} + </p> + <p> + This annotation tells the framework that all methods defined on this interface can be executed remotely. + It can be applied to super-interfaces, super-classes, etc..., and exposes the methods at whatever level it is defined. + </p> + <p> + The {@link org.apache.juneau.remoteable.RemoteMethod @RemoteMethod} annotation can also be used on individual methods + to tailor which methods are exposed or their paths. + </p> + <p> There are two ways to expose remoteable proxies on the server side: </p> - <ol> + <ol class='spaced-list'> <li>Extending from <code>RemoteableServlet</code>. <li>Using a <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code> annotation on a Java method. </ol> @@ -2310,24 +2361,21 @@ } </p> <p> - In either case, the proxy communications layer is pure REST. - Parameters passed in on the client side are serialized as an HTTP POST, parsed on the - server side, and then passed to the invocation method. The returned POJO is then marshalled back as an HTTP response. - Therefore, in cases where the interface classes are not available on the client side, + In either case, the proxy communications layer is pure REST. + <br>Therefore, in cases where the interface classes are not available on the client side, the same method calls can be made through pure REST calls. - This can also aid significantly in debugging, since calls to the remoteable service + <br>This can also aid significantly in debugging, since calls to the remoteable service can be made directly from a browser with no coding involved. </p> <p> The parameters and return types of the Java methods can be any of the supported serializable and parsable types in <a class='doclink' href='#Core.PojoCategories'>POJO Categories</a>. - This ends up being WAY more flexible than other proxy interfaces since Juneau can handle so may POJO types out-of-the-box. - Most of the time you don't even need to modify your existing Java implementation code. + <br>This ends up being WAY more flexible than other proxy interfaces since Juneau can handle so may POJO types out-of-the-box. + <br>Most of the time you don't even need to modify your existing Java implementation code. </p> <p> - The RemoteableServlet class itself shows how sophisticated REST interfaces can be built on the Juneau RestServlet - API using very little code. The RemoteableServlet class itself consists of only 53 lines of code, yet is - a sophisticated discoverable and self-documenting REST interface. And since the remote proxy API is built on top - of REST, it can be debugged using just a browser. + The RemoteableServlet class itself shows how sophisticated REST interfaces can be built on the Juneau RestServlet API using very little code. + <br>The RemoteableServlet class itself consists of only 53 lines of code, yet is a sophisticated discoverable and self-documenting REST interface. + <br>And since the remote proxy API is built on top of REST, it can be debugged using just a browser. </p> <p> The requirements for a method to be callable through a remoteable service are: @@ -2343,6 +2391,90 @@ <ul class='javahierarchy'> <li class='p'><a class='doclink' href='org/apache/juneau/server/remoteable/package-summary.html#TOC'>org.apache.juneau.rest.remoteable</a> - Juneau Remoteable API Javadocs. </ul> + + <!-- ======================================================================================================== --> + <a id="Remoteable.3rdParty"></a> + <h3 class='topic' onclick='toggle(this)'>6.1 - Interface proxies against 3rd-party REST interfaces</h3> + <div class='topic'> + <p> + Remoteable proxies can also be used to define interface proxies against 3rd-party REST interfaces. + This is an extremely powerful feature that allows you to quickly define easy-to-use interfaces against virtually any REST interface. + </p> + <p> + Similar in concept to remoteable services defined above, but in this case we simply define our interface with + special annotations that tell us how to convert input and output to HTTP headers, query paramters, form post parameters, or request/response bodies. + </p> + <p class='bcode'> + <ja>@Remoteable</ja> + <jk>public interface</jk> MyProxyInterface { + + <ja>@RemoteMethod</ja>(httpMethod=<js>"POST"</js>, path=<js>"/method"</js>) + String doMethod(<ja>@Header</ja>(<js>"E-Tag"</js>) UUID etag, <ja>@Query</ja>("debug") <jk>boolean</jk> debug, <ja>@Body</ja> MyPojo pojo); + } + + RestClient client = <jk>new</jk> RestClientBuilder().build(); + MyProxyInterface p = client.getRemoteableProxy(MyProxyInterface.<jk>class</jk>, <js>"http://hostname/some/rest/interface"</js>); + String response = p.doMethod(UUID.<jsm>generate</jsm>(), <jk>true</jk>, <jk>new</jk> MyPojo()); + </p> + <p> + The Java method arguements can be annotated with any of the following: + </p> + <ul class='spaced-list'> + <li>{@link org.apache.juneau.remoteable.Query @Query} - A URL query parameter. + <br>The argument can be any of the following types: + <ul> + <li>Any serializable POJO - Converted to text using {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. + <li><code>Map<String,Object></code> - Individual name-value pairs. + <br>Values are converted to text using {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. + <li><code>String</code> - Treated as a query string. + </ul> + <li>{@link org.apache.juneau.remoteable.QueryIfNE @QueryIfNE} - Same as <ja>@Query</ja> except skips the value if it's null/empty. + <li>{@link org.apache.juneau.remoteable.FormData @FormData} - A form-data parameter. + <br>Note that this is only available if the HTTP method is <code>POST</code>. + <br>The argument can be any of the following types: + <ul> + <li>Any serializable POJO - Converted to text using {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. + <li>{@link org.apache.juneau.rest.client.NameValuePairs} - Individual name-value pairs. + <li><code>Map<String,Object></code> - Individual name-value pairs. + <br>Values are converted to text using {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. + </ul> + <li>{@link org.apache.juneau.remoteable.FormDataIfNE @FormDataIfNE} - Same as <ja>@FormData</ja> except skips the value if it's null/empty. + <li>{@link org.apache.juneau.remoteable.Header @Header} - A request header. + <br>The argument can be any of the following types: + <ul> + <li>Any serializable POJO - Converted to text using {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. + <li><code>Map<String,Object></code> - Individual name-value pairs. + <br>Values are converted to text using {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. + </ul> + <li>{@link org.apache.juneau.remoteable.HeaderIfNE @HeaderIfNE} - Same as <ja>@Header</ja> except skips the value if it's null/empty. + <li>{@link org.apache.juneau.remoteable.Body @Body} - The HTTP request body. + <br>The argument can be any of the following types: + <ul> + <li>Any serializable POJO - Converted to text/bytes using the {@link org.apache.juneau.serializer.Serializer} registered with the <code>RestClient</code>. + <li>{@link java.io.Reader} - Raw contents of reader will be serialized to remote resource. + <li>{@link java.io.InputStream} - Raw contents of input stream will be serialized to remote resource. + <li>{@link org.apache.http.HttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. + <li>{@link org.apache.juneau.rest.client.NameValuePairs} - Converted to a URL-encoded FORM post. + </ul> + </ul> + <p> + The return type of the Java method can be any of the following: + </p> + <ul class='spaced-list'> + <li><jk>void</jk> - Don't parse any response. + <br>Note that the method will still throw a runtime exception if an error HTTP status is returned. + <li>Any parsable POJO - The body of the response will be converted to the POJO using the parser defined on the <code>RestClient</code>. + <li><code>HttpResponse</code> - Returns the raw <code>HttpResponse</code> returned by the inner <code>HttpClient</code>. + <li>{@link java.io.Reader} - Returns access to the raw reader of the response. + <br>Note that if you don't want your response parsed as a POJO, you'll want to get the response reader directly. + <li>{@link java.io.InputStream} - Returns access to the raw input stream of the response. + </ul> + + <h6 class='topic'>Additional Information</h6> + <ul class='javahierarchy'> + <li class='p'><a class='doclink' href='org/apache/juneau/remoteable/package-summary.html#TOC'>org.apache.juneau.remoteable</a> - Juneau Remoteable API Javadocs. + </ul> + </div> </div> <!-- ======================================================================================================== --> @@ -4345,8 +4477,6 @@ As good practice, you'll want to use interfaces to prevent all public methods from being exposed. </p> <p> - The {@link org.apache.juneau.rest.client.RestClientBuilder#remoteableServletUri(String)} method is used to specify the location - of the remoteable services servlet running on the server. Proxy interfaces are then retrieved using the {@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class)} method. </p> <p> @@ -4355,7 +4485,7 @@ <p class='bcode'> <jc>// Create a RestClient using JSON for serialization, and point to the server-side remoteable servlet.</jc> RestClient client = <jk>new</jk> RestClientBuilder() - .remoteableServletUri(<js>"http://localhost:10000/remoteable"</js>) + .rootUrl(<js>"http://localhost:10000/remoteable"</js>) .build(); <jc>// Create a proxy interface.</jc> @@ -4373,8 +4503,8 @@ <li class='a'>{@link org.apache.juneau.rest.remoteable.RemoteableServlet} <li class='c'>{@link org.apache.juneau.rest.client.RestClient} <ul> - <li class='m'>{@link org.apache.juneau.rest.client.RestClientBuilder#remoteableServletUri(String) remoteableServletUri(String)} <li class='m'>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class) getRemoteableProxy(Class)} + <li class='m'>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object) getRemoteableProxy(Class,Object)} </ul> </ul> </div> @@ -5757,6 +5887,17 @@ <li>{@link org.apache.juneau.dto.html5.HtmlElementMixed#getChild(int...)} </ul> <li>New common serializer setting: {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_abridged}. + <li>Support for defining interface proxies against 3rd-party REST interfaces. + <br>New package {@link org.apache.juneau.remoteable} for all remoteable proxy interface annotations. + <br><ja>@Remoteable</ja> annotation has been moved to this package. + <li>Updated doc: <a class='doclink' href='#Remoteable'>6 - Remoteable Services</a> + <li>New doc: <a class='doclink' href='#Remoteable.3rdParty'>6.1 - Interface proxies against 3rd-party REST interfaces</a> + <li>New URL-encoding serializer setting: {@link org.apache.juneau.urlencoding.UrlEncodingSerializerContext#URLENC_paramFormat} + <li>New methods on {@link org.apache.juneau.urlencoding.UrlEncodingSerializerBuilder}: + <ul> + <li>{@link org.apache.juneau.urlencoding.UrlEncodingSerializerBuilder#paramFormat(String) paramFormat(String)} + <li>{@link org.apache.juneau.urlencoding.UrlEncodingSerializerBuilder#plainTextParams() plainTextParams()} + </ul> </ul> <h6 class='topic'>org.apache.juneau.rest</h6> @@ -5858,7 +5999,6 @@ IAddressBook ab = client.getRemoteableProxy(IAddressBook.<jk>class</jk>, <js>"/addressBook/myproxy"</js>); </p> See {@link org.apache.juneau.rest.annotation.RestMethod#name()} for more information. - <li>Updated doc: <a class='doclink' href='#Remoteable'>6 - Remoteable Services</a> <li>{@link org.apache.juneau.rest.RestRequest#toString()} can be called at any time to view the headers and content of the request without affecting functionality. Very useful for debugging. <li>You can now use numeric values in path annotations. @@ -5876,47 +6016,76 @@ <h6 class='topic'>org.apache.juneau.rest.client</h6> <ul class='spaced-list'> <li>Revamped the client API to use builders. - <li>New methods added to {@link org.apache.juneau.rest.client.RestCall}: - <ul> - <li>{@link org.apache.juneau.rest.client.RestCall#serializer(Serializer)} - Override the serializer defined on the client for a single call. - <li>{@link org.apache.juneau.rest.client.RestCall#parser(Parser)} - Override the parser defined on the client for a single call. - </ul> - <li>New methods added to {@link org.apache.juneau.rest.client.RestClient}: + <li>New doc: <a class='doclink' href='org/apache/juneau/rest/client/package-summary.html#Debugging'>1.5 - Debugging</a> + <li>The <code>RestClient</code> class <code>doX(Object url)</code> methods now handle HttpClient <code>URIBuilder</code> instances. + <li>New methods added/updated to {@link org.apache.juneau.rest.client.RestClient}: <ul> - <li>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object)} - For interface proxies defined using <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>. - <li>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object,Serializer,Parser)} - Same as above, but overrides the serializer and parser defined on the client. + <li>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object) getRemoteableProxy(Class,Object)} - For interface proxies defined using <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>. + <li>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object,Serializer,Parser) getRemoteableProxy(Class,Object,Serializer,Parser)} - Same as above, but overrides the serializer and parser defined on the client. + <li>{@link org.apache.juneau.rest.client.RestClient#doPost(Object) doPost(Object)} + <li>{@link org.apache.juneau.rest.client.RestClient#doCall(HttpMethod,Object,Object) doCall(HttpMethod,Object,Object)} - Can now pass in instances of {@link org.apache.juneau.rest.client.NameValuePairs} for easy form posts. + <br>This extends to all methods that take in the input. </ul> + <li>New methods on {@link org.apache.juneau.rest.client.RestCall}: + <ul> + <li>{@link org.apache.juneau.rest.client.RestCall#uri(Object) uri(Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#query(String,Object,boolean) query(String,Object,boolean)} + <li>{@link org.apache.juneau.rest.client.RestCall#query(String,Object) query(String,Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#queryIfNE(String,Object) queryIfNE(String,Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#query(Map) query(Map)} + <li>{@link org.apache.juneau.rest.client.RestCall#queryIfNE(Map) queryIfNE(Map)} + <li>{@link org.apache.juneau.rest.client.RestCall#query(String) query(String)} + <li>{@link org.apache.juneau.rest.client.RestCall#formData(String,Object,boolean) formData(String,Object,boolean)} + <li>{@link org.apache.juneau.rest.client.RestCall#formData(String,Object) formData(String,Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#formDataIfNE(String,Object) formDataIfNE(String,Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#formData(Map) formData(Map)} + <li>{@link org.apache.juneau.rest.client.RestCall#formDataIfNE(Map) formDataIfNE(Map)} + <li>{@link org.apache.juneau.rest.client.RestCall#header(String,Object,boolean) header(String,Object,boolean)} + <li>{@link org.apache.juneau.rest.client.RestCall#header(String,Object) header(String,Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#headerIfNE(String,Object) headerIfNE(String,Object)} + <li>{@link org.apache.juneau.rest.client.RestCall#headers(Map) headers(Map)} + <li>{@link org.apache.juneau.rest.client.RestCall#headersIfNE(Map) headersIfNE(Map)} + <li>{@link org.apache.juneau.rest.client.RestCall#host(String) host(String)} + <li>{@link org.apache.juneau.rest.client.RestCall#port(int) port(int)} + <li>{@link org.apache.juneau.rest.client.RestCall#userInfo(String,String) userInfo(String,String)} + <li>{@link org.apache.juneau.rest.client.RestCall#userInfo(String) userInfo(String)} + <li>{@link org.apache.juneau.rest.client.RestCall#scheme(String) scheme(String)} + </ul> <li>New methods added to {@link org.apache.juneau.rest.client.RestClientBuilder}: - <ul> - <li>{@link org.apache.juneau.rest.client.RestClientBuilder#noTrace()} - Adds a <code>No-Trace: true</code> header on all requests to prevent - the servlet from logging errors. - Useful for testing scenarios when you don't want the console to end up showing errors done on purpose. - <li>{@link org.apache.juneau.rest.client.RestClientBuilder#debug(boolean)} now adds a <code>Debug: true</code> header on all requests. - </ul> - <li>New doc: <a class='doclink' href='org/apache/juneau/rest/client/package-summary.html#Debugging'>1.5 - Debugging</a> - <li>The <code>RestClient</code> class <code>doX(Object url)</code> methods now handle HttpClient <code>URIBuilder</code> instances. - <li>New methods added to {@link org.apache.juneau.rest.client.RestCall} that allow you to manipulate URLs after you've already created the <code>RestCall</code> object: <ul> - <li>{@link org.apache.juneau.rest.client.RestCall#uri(Object)} - <li>{@link org.apache.juneau.rest.client.RestCall#param(String,Object)} - <li>{@link org.apache.juneau.rest.client.RestCall#paramIfNE(String,Object)} - <li>{@link org.apache.juneau.rest.client.RestCall#paramIfNN(String,Object)} - <li>{@link org.apache.juneau.rest.client.RestCall#params(Map)} - <li>{@link org.apache.juneau.rest.client.RestCall#query(String)} - <li>{@link org.apache.juneau.rest.client.RestCall#host(String)} - <li>{@link org.apache.juneau.rest.client.RestCall#port(int)} - <li>{@link org.apache.juneau.rest.client.RestCall#userInfo(String,String)} - <li>{@link org.apache.juneau.rest.client.RestCall#userInfo(String)} - <li>{@link org.apache.juneau.rest.client.RestCall#scheme(String)} + <li>{@link org.apache.juneau.rest.client.RestClientBuilder#executorService(ExecutorService,boolean) executorService(ExecutorService,boolean)} + <li>{@link org.apache.juneau.rest.client.RestClientBuilder#paramFormat(String) paramFormat(ExecutorService,boolean)} + <li>{@link org.apache.juneau.rest.client.RestClientBuilder#plainTextParams() plainTextParams()} + <li>{@link org.apache.juneau.rest.client.RestClientBuilder#noTrace() noTrace()} - Adds a <code>No-Trace: true</code> header on all requests to prevent + the servlet from logging errors. + <br>Useful for testing scenarios when you don't want the console to end up showing errors done on purpose. + <li>{@link org.apache.juneau.rest.client.RestClientBuilder#debug(boolean) debug(boolean)} now adds a <code>Debug: true</code> header on all requests. </ul> - <li>New methods added to allow for asynchronous HTTP calls: + <li>New methods added/updated to {@link org.apache.juneau.rest.client.RestCall}: + <ul> + <li>{@link org.apache.juneau.rest.client.RestCall#runFuture() runFuture()} + <li>{@link org.apache.juneau.rest.client.RestCall#getResponseFuture(Class) getResponseFuture(Class)} + <li>{@link org.apache.juneau.rest.client.RestCall#getResponseFuture(Type,Type...) getResponseFuture(Type,Type...)} + <li>{@link org.apache.juneau.rest.client.RestCall#getResponseAsStringFuture() getResponseAsStringFuture()} + <li>{@link org.apache.juneau.rest.client.RestCall#serializer(Serializer) serializer(Serializer)} - Override the serializer defined on the client for a single call. + <li>{@link org.apache.juneau.rest.client.RestCall#parser(Parser) parser(Parser)} - Override the parser defined on the client for a single call. + <li>{@link org.apache.juneau.rest.client.RestCall#input(Object) input(Object)} - Now accepts instances of {@link org.apache.juneau.rest.client.NameValuePairs}. + <li>{@link org.apache.juneau.rest.client.RestCall#getResponse(Class) getResponse(Class)} - Can now pass in any of the following: + <ul> + <li>{@link org.apache.http.HttpResponse} - Returns the raw <code>HttpResponse</code> returned by the inner <code>HttpClient</code>. + <li>{@link java.io.Reader} - Returns access to the raw reader of the response. + <li>{@link java.io.InputStream} - Returns access to the raw input stream of the response. + </ul> + </ul> + <li>New methods added to {@link org.apache.juneau.rest.client.NameValuePairs}: <ul> - <li>{@link org.apache.juneau.rest.client.RestClientBuilder#executorService(ExecutorService,boolean)} - <li>{@link org.apache.juneau.rest.client.RestCall#runFuture()} - <li>{@link org.apache.juneau.rest.client.RestCall#getResponseFuture(Class)} - <li>{@link org.apache.juneau.rest.client.RestCall#getResponseFuture(Type,Type...)} - <li>{@link org.apache.juneau.rest.client.RestCall#getResponseAsStringFuture()} + <li>{@link org.apache.juneau.rest.client.NameValuePairs#append(String,Object) append(String,Object)} + <li>{@link org.apache.juneau.rest.client.NameValuePairs#append(String,Object,UrlEncodingSerializer) append(String,Object,UrlEncodingSerializer)} </ul> + <li>{@link org.apache.juneau.rest.client.RetryOn} is now an abstract class with an additional method: + <ul> + <li>{@link org.apache.juneau.rest.client.RetryOn#onResponse(HttpResponse) onResponse(HttpResponse)} + </ul> </ul> <h6 class='topic'>org.apache.juneau.microservice</h6> @@ -5932,7 +6101,6 @@ </ul> </div> - <!-- ======================================================================================================== --> <a id="6.1.0"></a> <h3 class='topic' onclick='toggle(this)'>6.1.0 (Feb 25, 2017)</h3> @@ -6141,26 +6309,27 @@ <li>New {@link org.apache.juneau.rest.RestRequest#getTimeZone()} method. <li>Standardized the following methods in {@link org.apache.juneau.rest.RestRequest} to remove dependency on <code>ClassMeta</code> objects and eliminate the need for casts: - <ul> - <li>{@link org.apache.juneau.rest.RestRequest#getHeader(String,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getHeader(String,Object,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getHeader(String,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Object,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Object,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameters(String,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameters(String,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameter(String,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameter(String,Object,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameters(String,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameter(String,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameters(String,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getPathParameter(String,Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getPathParameter(String,Type,Type...)} - <li>{@link org.apache.juneau.rest.RestRequest#getBody(Class)} - <li>{@link org.apache.juneau.rest.RestRequest#getBody(Type,Type...)} - </ul> + <ul> + <li>{@link org.apache.juneau.rest.RestRequest#getHeader(String,Class) getHeader(String,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getHeader(String,Object,Class) getHeader(String)} + <li>{@link org.apache.juneau.rest.RestRequest#getHeader(String,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Object,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameter(String,Object,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameters(String,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getQueryParameters(String,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameter(String,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameter(String,Object,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameters(String,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameter(String,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getFormDataParameters(String,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getPathParameter(String,Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getPathParameter(String,Type,Type...)} + <li>{@link org.apache.juneau.rest.RestRequest#getBody(Class)} + <li>{@link org.apache.juneau.rest.RestRequest#getBody(Type,Type...)} + </ul> + <li>New methods on {@link org.apache.juneau.rest.client.NameValuePairs} <li>Fixed issue where whitespace was not added to UON/URL-Encoding output when <code>&plainText=true</code> specified. </ul> </div> @@ -7284,7 +7453,7 @@ Adds <js>"* {white-space:nowrap}"</js> to the style header to prevent word wrapping. <li>Fixed bug in {@link org.apache.juneau.uon.UonParser} where passing in a blank value on an array or collection type in a form post would cause a <code>ClassCastException</code>. New behavior creates an empty array or <code>Collection</code>. - <li>Improved implementation of {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializeUrlPart(Object)} method. + <li>Improved implementation of <del><code>UrlEncodingSerializer.serializeUrlPart(Object)</code></del> method. </ul> <h6 class='topic'>Server</h6> @@ -7609,7 +7778,7 @@ <li>New {@link org.apache.juneau.rest.Redirect} class that simplifies performing redirections in REST methods. <li>New pluggable {@link org.apache.juneau.rest.ResponseHandler} class and {@link org.apache.juneau.rest.annotation.RestResource#responseHandlers()} annotation for defining customer response handlers for special kinds of POJOs. - <li>New method {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#serializeUrlPart(Object)} method. + <li>New method <del><code>UrlEncodingSerializer.serializeUrlPart(Object)</code></del> method. <li>New method {@link org.apache.juneau.rest.RestRequest#getServletURIBuilder()} for construcing servlet-based URLs more efficiently. <li>New method {@link org.apache.juneau.rest.RestResponse#getNegotiatedOutputStream()} that uses encoders if a match is found, and {@link org.apache.juneau.rest.RestResponse#getOutputStream()} that just return the underlying output stream without any modifications. http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java ---------------------------------------------------------------------- diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java index 1c7431a..e120a67 100755 --- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java +++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java @@ -14,10 +14,13 @@ package org.apache.juneau.examples.addressbook; import java.util.*; +import org.apache.juneau.remoteable.*; + /** * Interface used to help illustrate proxy interfaces. * See {@link SampleRemoteableServlet}. */ +@Remoteable public interface IAddressBook { /** Initialize this address book with preset entries */ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/SampleRemoteableServicesResourceTest.java ---------------------------------------------------------------------- diff --git a/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/SampleRemoteableServicesResourceTest.java b/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/SampleRemoteableServicesResourceTest.java index 1eca174..cbdce58 100644 --- a/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/SampleRemoteableServicesResourceTest.java +++ b/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/SampleRemoteableServicesResourceTest.java @@ -15,8 +15,9 @@ package org.apache.juneau.examples.rest; import static org.apache.juneau.xml.XmlSerializerContext.*; import static org.junit.Assert.*; +import java.text.*; + import org.apache.juneau.examples.addressbook.*; -import org.apache.juneau.json.*; import org.apache.juneau.rest.client.*; import org.apache.juneau.transforms.*; import org.apache.juneau.uon.*; @@ -26,22 +27,21 @@ import org.junit.*; public class SampleRemoteableServicesResourceTest extends RestTestcase { static RestClient[] clients; + + private static String path = SamplesMicroservice.getURI().getPath() + "/addressBook/proxy"; @BeforeClass public static void beforeClass() throws Exception { clients = new RestClient[] { SamplesMicroservice.client() .pojoSwaps(CalendarSwap.DateMedium.class) - .remoteableServletUri("/remoteable") .build(), SamplesMicroservice.client(XmlSerializer.class, XmlParser.class) .pojoSwaps(CalendarSwap.DateMedium.class) - .remoteableServletUri("/remoteable") .property(XML_autoDetectNamespaces, true) .build(), SamplesMicroservice.client(UonSerializer.class, UonParser.class) .pojoSwaps(CalendarSwap.DateMedium.class) - .remoteableServletUri("/remoteable") .build(), }; } @@ -59,16 +59,19 @@ public class SampleRemoteableServicesResourceTest extends RestTestcase { @Test public void testBasic() throws Exception { for (RestClient client : clients) { - IAddressBook ab = client.getRemoteableProxy(IAddressBook.class); + IAddressBook ab = client.getRemoteableProxy(IAddressBook.class, path); Person p = ab.createPerson( new CreatePerson("Test Person", AddressBook.toCalendar("Aug 1, 1999"), new CreateAddress("Test street", "Test city", "Test state", 12345, true)) ); - assertEquals( - "{_type:'person',id:x,name:'Test Person',birthDate:'Aug 1, 1999',addresses:[{id:x,street:'Test street',city:'Test city',state:'Test state',zip:12345,isCurrent:true}],age:x}", - JsonSerializer.DEFAULT_LAX.toString(p).replaceAll("id:\\d+", "id:x").replaceAll("age:\\d+", "age:x")); + assertEquals("Test Person", p.name); + assertEquals("Aug 1, 1999", DateFormat.getDateInstance(DateFormat.MEDIUM).format(p.birthDate.getTime())); + assertEquals("Test street", p.addresses.get(0).street); + assertEquals("Test city", p.addresses.get(0).city); + assertEquals("Test state", p.addresses.get(0).state); + assertEquals(12345, p.addresses.get(0).zip); + assertEquals(true, p.addresses.get(0).isCurrent); } } - } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html ---------------------------------------------------------------------- diff --git a/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html b/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html index dcdf2c5..614e64a 100755 --- a/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html +++ b/juneau-microservice/src/main/java/org/apache/juneau/microservice/package.html @@ -670,7 +670,7 @@ <li><l>$R{trimmedRequestURI}</l> - Value returned by {@link org.apache.juneau.rest.RestRequest#getTrimmedRequestURI()}. </ul> <li><l>$SA{key,mediaType}</l> - Object returned by {@link org.apache.juneau.rest.RestRequest#getAttribute(String)} converted to a string using the serializer registered to handle the specified media type. - <li><l>$UE{...}</l> - URL-Encode the specified value by calling {@link org.apache.juneau.rest.RestUtils#encode(String)}. + <li><l>$UE{...}</l> - URL-Encode the specified value. </ul> <h6 class='figure'>Example usage:</h6> <p class='bcode'> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7139635d/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java ---------------------------------------------------------------------- diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java index ee97ac1..845f520 100644 --- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java +++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java @@ -16,17 +16,23 @@ import java.util.*; import org.apache.http.*; import org.apache.http.client.entity.*; +import org.apache.http.message.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.urlencoding.*; /** * Convenience class for constructing instances of <code>List<NameValuePair></code> * for the {@link UrlEncodedFormEntity} class. + * <p> + * Instances of this method can be passed directly to the {@link RestClient#doPost(Object, Object)} method or + * {@link RestCall#input(Object)} methods to perform URL-encoded form posts. * * <h5 class='section'>Example:</h5> * <p class='bcode'> * NameValuePairs params = <jk>new</jk> NameValuePairs() - * .append(<jk>new</jk> BasicNameValuePair(<js>"j_username"</js>, user)) - * .append(<jk>new</jk> BasicNameValuePair(<js>"j_password"</js>, pw)); - * request.setEntity(<jk>new</jk> UrlEncodedFormEntity(params)); + * .append(<js>"j_username"</js>, user) + * .append(<js>"j_password"</js>, pw); + * restClient.doPost(url, params).run(); * </p> */ public final class NameValuePairs extends LinkedList<NameValuePair> { @@ -43,4 +49,34 @@ public final class NameValuePairs extends LinkedList<NameValuePair> { super.add(pair); return this; } + + /** + * Appends the specified name/value pair to the end of this list. + * <p> + * The value is simply converted to a string using <code>toString()</code>, or <js>"null"</js> if <jk>null</jk>. + * + * @param name The pair name. + * @param value The pair value. + * @return This object (for method chaining). + */ + public NameValuePairs append(String name, Object value) { + super.add(new BasicNameValuePair(name, StringUtils.toString(value))); + return this; + } + + /** + * Appends the specified name/value pair to the end of this list. + * <p> + * The value is converted to UON notation using the {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)} method + * of the specified serializer. + * + * @param name The pair name. + * @param value The pair value. + * @param serializer The serializer to use to convert the value to a string. + * @return This object (for method chaining). + */ + public NameValuePairs append(String name, Object value, UrlEncodingSerializer serializer) { + super.add(new SerializedNameValuePair(name, value, serializer)); + return this; + } }
