001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.uon; 014 015import java.lang.reflect.*; 016import java.util.*; 017import java.util.concurrent.*; 018 019import org.apache.juneau.*; 020import org.apache.juneau.annotation.*; 021import org.apache.juneau.collections.*; 022import org.apache.juneau.httppart.*; 023import org.apache.juneau.parser.*; 024 025/** 026 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models. 027 * 028 * <h5 class='topic'>Media types</h5> 029 * 030 * Handles <c>Content-Type</c> types: <bc>text/uon</bc> 031 * 032 * <h5 class='topic'>Description</h5> 033 * 034 * This parser uses a state machine, which makes it very fast and efficient. 035 */ 036@ConfigurableContext 037public class UonParser extends ReaderParser implements HttpPartParser, UonMetaProvider, UonCommon { 038 039 //------------------------------------------------------------------------------------------------------------------- 040 // Configurable properties 041 //------------------------------------------------------------------------------------------------------------------- 042 043 static final String PREFIX = "UonParser"; 044 045 /** 046 * Configuration property: Decode <js>"%xx"</js> sequences. 047 * 048 * <h5 class='section'>Property:</h5> 049 * <ul class='spaced-list'> 050 * <li><b>ID:</b> {@link org.apache.juneau.uon.UonParser#UON_decoding UON_decoding} 051 * <li><b>Name:</b> <js>"UonParser.decoding.b"</js> 052 * <li><b>Data type:</b> <jk>boolean</jk> 053 * <li><b>System property:</b> <c>UonParser.decoding</c> 054 * <li><b>Environment variable:</b> <c>UONPARSER_DECODING</c> 055 * <li><b>Default:</b> <jk>false</jk> for {@link org.apache.juneau.uon.UonParser}, <jk>true</jk> for {@link org.apache.juneau.urlencoding.UrlEncodingParser} 056 * <li><b>Session property:</b> <jk>false</jk> 057 * <li><b>Annotations:</b> 058 * <ul> 059 * <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#decoding()} 060 * </ul> 061 * <li><b>Methods:</b> 062 * <ul> 063 * <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding(boolean)} 064 * <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding()} 065 * </ul> 066 * </ul> 067 * 068 * <h5 class='section'>Description:</h5> 069 * <p> 070 * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded 071 * before being passed to this parser. 072 * 073 * <h5 class='section'>Example:</h5> 074 * <p class='bcode w800'> 075 * <jc>// Create a decoding UON parser.</jc> 076 * ReaderParser p = UonParser. 077 * .<jsm>create</jsm>() 078 * .decoding() 079 * .build(); 080 * 081 * <jc>// Same, but use property.</jc> 082 * ReaderParser p = UonParser. 083 * .<jsm>create</jsm>() 084 * .set(<jsf>UON_decoding</jsf>, <jk>true</jk>) 085 * .build(); 086 * 087 * <jc>// Produces: ["foo bar", "baz quz"].</jc> 088 * String[] foo = p.parse(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>); 089 * </p> 090 */ 091 public static final String UON_decoding = PREFIX + ".decoding.b"; 092 093 /** 094 * Configuration property: Validate end. 095 * 096 * <h5 class='section'>Property:</h5> 097 * <ul class='spaced-list'> 098 * <li><b>ID:</b> {@link org.apache.juneau.uon.UonParser#UON_validateEnd UON_validateEnd} 099 * <li><b>Name:</b> <js>"UonParser.validateEnd.b"</js> 100 * <li><b>Data type:</b> <jk>boolean</jk> 101 * <li><b>System property:</b> <c>UonParser.validateEnd</c> 102 * <li><b>Environment variable:</b> <c>UONPARSER_VALIDATEEND</c> 103 * <li><b>Default:</b> <jk>false</jk> 104 * <li><b>Session property:</b> <jk>false</jk> 105 * <li><b>Annotations:</b> 106 * <ul> 107 * <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#validateEnd()} 108 * </ul> 109 * <li><b>Methods:</b> 110 * <ul> 111 * <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#validateEnd()} 112 * </ul> 113 * </ul> 114 * 115 * <h5 class='section'>Description:</h5> 116 * <p> 117 * If <jk>true</jk>, after parsing a POJO from the input, verifies that the remaining input in 118 * the stream consists of only comments or whitespace. 119 * 120 * <h5 class='section'>Example:</h5> 121 * <p class='bcode w800'> 122 * <jc>// Create a parser using strict mode.</jc> 123 * ReaderParser p = UonParser. 124 * .<jsm>create</jsm>() 125 * .validateEnd() 126 * .build(); 127 * 128 * <jc>// Same, but use property.</jc> 129 * ReaderParser p = UonParser. 130 * .<jsm>create</jsm>() 131 * .set(<jsf>UON_validateEnd</jsf>, <jk>true</jk>) 132 * .build(); 133 * 134 * <jc>// Should fail because input has multiple POJOs.</jc> 135 * String in = <js>"(foo=bar)(baz=qux)"</js>; 136 * MyBean myBean = p.parse(in, MyBean.<jk>class</jk>); 137 * </p> 138 */ 139 public static final String UON_validateEnd = PREFIX + ".validateEnd.b"; 140 141 //------------------------------------------------------------------------------------------------------------------- 142 // Predefined instances 143 //------------------------------------------------------------------------------------------------------------------- 144 145 /** Reusable instance of {@link UonParser}, all default settings. */ 146 public static final UonParser DEFAULT = new UonParser(PropertyStore.DEFAULT); 147 148 /** Reusable instance of {@link UonParser} with decodeChars set to true. */ 149 public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(PropertyStore.DEFAULT); 150 151 152 //------------------------------------------------------------------------------------------------------------------- 153 // Predefined subclasses 154 //------------------------------------------------------------------------------------------------------------------- 155 156 /** Default parser, decoding. */ 157 public static class Decoding extends UonParser { 158 159 /** 160 * Constructor. 161 * 162 * @param ps The property store containing all the settings for this object. 163 */ 164 public Decoding(PropertyStore ps) { 165 super(ps.builder().setDefault(UON_decoding, true).build()); 166 } 167 } 168 169 /** 170 * Converts the specified input to the specified class type. 171 * 172 * @param partType The part type being parsed. 173 * @param schema 174 * Schema information about the part. 175 * <br>May be <jk>null</jk>. 176 * <br>Not all part parsers use the schema information. 177 * @param in The input being parsed. 178 * @param toType The POJO type to transform the input into. 179 * @return The parsed value. 180 * @throws ParseException Malformed input encountered. 181 * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation. 182 */ 183 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> toType) throws ParseException, SchemaValidationException { 184 return createPartSession(null).parse(partType, schema, in, toType); 185 } 186 187 /** 188 * Converts the specified input to the specified class type. 189 * 190 * @param partType The part type being parsed. 191 * @param schema 192 * Schema information about the part. 193 * <br>May be <jk>null</jk>. 194 * <br>Not all part parsers use the schema information. 195 * @param in The input being parsed. 196 * @param toType The POJO type to transform the input into. 197 * @return The parsed value. 198 * @throws ParseException Malformed input encountered. 199 * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation. 200 */ 201 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException { 202 return createPartSession(null).parse(partType, schema, in, getClassMeta(toType)); 203 } 204 205 /** 206 * Converts the specified input to the specified class type. 207 * 208 * @param partType The part type being parsed. 209 * @param schema 210 * Schema information about the part. 211 * <br>May be <jk>null</jk>. 212 * <br>Not all part parsers use the schema information. 213 * @param in The input being parsed. 214 * @param toType The POJO type to transform the input into. 215 * @param toTypeArgs The generic type arguments of the POJO type to transform the input into. 216 * @return The parsed value. 217 * @throws ParseException Malformed input encountered. 218 * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation. 219 */ 220 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException { 221 return createPartSession(null).parse(partType, schema, in, getClassMeta(toType, toTypeArgs)); 222 } 223 224 //------------------------------------------------------------------------------------------------------------------- 225 // Instance 226 //------------------------------------------------------------------------------------------------------------------- 227 228 private final boolean 229 decoding, validateEnd; 230 private final Map<ClassMeta<?>,UonClassMeta> uonClassMetas = new ConcurrentHashMap<>(); 231 private final Map<BeanPropertyMeta,UonBeanPropertyMeta> uonBeanPropertyMetas = new ConcurrentHashMap<>(); 232 233 /** 234 * Constructor. 235 * 236 * @param ps 237 * The property store containing all the settings for this object. 238 */ 239 public UonParser(PropertyStore ps) { 240 this(ps, "text/uon"); 241 } 242 243 /** 244 * No-arg constructor. 245 */ 246 public UonParser() { 247 this(PropertyStore.DEFAULT, "text/uon"); 248 } 249 250 /** 251 * Constructor. 252 * 253 * @param ps 254 * The property store containing all the settings for this object. 255 * @param consumes 256 * The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*​/json"</js>). 257 */ 258 public UonParser(PropertyStore ps, String...consumes) { 259 super(ps, consumes); 260 this.decoding = getBooleanProperty(UON_decoding, false); 261 this.validateEnd = getBooleanProperty(UON_validateEnd, false); 262 } 263 264 @Override /* Context */ 265 public UonParserBuilder builder() { 266 return new UonParserBuilder(getPropertyStore()); 267 } 268 269 /** 270 * Instantiates a new clean-slate {@link UonParserBuilder} object. 271 * 272 * <p> 273 * This is equivalent to simply calling <code><jk>new</jk> UonParserBuilder()</code>. 274 * 275 * <p> 276 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 277 * the settings of the object called on. 278 * 279 * @return A new {@link UonParserBuilder} object. 280 */ 281 public static UonParserBuilder create() { 282 return new UonParserBuilder(); 283 } 284 285 /** 286 * Create a UON parser session for parsing parameter values. 287 * 288 * @return A new parser session. 289 */ 290 protected final UonParserSession createParameterSession() { 291 return new UonParserSession(this, createDefaultSessionArgs(), false); 292 } 293 294 //----------------------------------------------------------------------------------------------------------------- 295 // Entry point methods 296 //----------------------------------------------------------------------------------------------------------------- 297 298 @Override /* Parser */ 299 public UonParserSession createSession(ParserSessionArgs args) { 300 return new UonParserSession(this, args); 301 } 302 303 @Override /* HttpPartParser */ 304 public UonParserSession createSession() { 305 return createSession(null); 306 } 307 308 @Override /* HttpPartParser */ 309 public UonParserSession createPartSession(ParserSessionArgs args) { 310 return new UonParserSession(this, args); 311 } 312 313 //----------------------------------------------------------------------------------------------------------------- 314 // Extended metadata 315 //----------------------------------------------------------------------------------------------------------------- 316 317 @Override /* UonMetaProvider */ 318 public UonClassMeta getUonClassMeta(ClassMeta<?> cm) { 319 UonClassMeta m = uonClassMetas.get(cm); 320 if (m == null) { 321 m = new UonClassMeta(cm, this); 322 uonClassMetas.put(cm, m); 323 } 324 return m; 325 } 326 327 @Override /* UonMetaProvider */ 328 public UonBeanPropertyMeta getUonBeanPropertyMeta(BeanPropertyMeta bpm) { 329 if (bpm == null) 330 return UonBeanPropertyMeta.DEFAULT; 331 UonBeanPropertyMeta m = uonBeanPropertyMetas.get(bpm); 332 if (m == null) { 333 m = new UonBeanPropertyMeta(bpm.getDelegateFor(), this); 334 uonBeanPropertyMetas.put(bpm, m); 335 } 336 return m; 337 } 338 339 //----------------------------------------------------------------------------------------------------------------- 340 // Properties 341 //----------------------------------------------------------------------------------------------------------------- 342 343 /** 344 * Decode <js>"%xx"</js> sequences enabled 345 * 346 * @see #UON_decoding 347 * @return 348 * <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded 349 * before being passed to this parser. 350 */ 351 protected final boolean isDecoding() { 352 return decoding; 353 } 354 355 /** 356 * Validate end enabled. 357 * 358 * @see #UON_validateEnd 359 * @return 360 * <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in 361 * the stream consists of only comments or whitespace. 362 */ 363 protected final boolean isValidateEnd() { 364 return validateEnd; 365 } 366 367 //----------------------------------------------------------------------------------------------------------------- 368 // Other methods 369 //----------------------------------------------------------------------------------------------------------------- 370 371 @Override /* Context */ 372 public OMap toMap() { 373 return super.toMap() 374 .a("UonParser", new DefaultFilteringOMap() 375 .a("decoding", decoding) 376 .a("validateEnd", validateEnd) 377 ); 378 } 379}