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.httppart.*; 022import org.apache.juneau.parser.*; 023 024/** 025 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models. 026 * 027 * <h5 class='topic'>Media types</h5> 028 * 029 * Handles <c>Content-Type</c> types: <bc>text/uon</bc> 030 * 031 * <h5 class='topic'>Description</h5> 032 * 033 * This parser uses a state machine, which makes it very fast and efficient. 034 */ 035@ConfigurableContext 036public class UonParser extends ReaderParser implements HttpPartParser, UonMetaProvider, UonCommon { 037 038 //------------------------------------------------------------------------------------------------------------------- 039 // Configurable properties 040 //------------------------------------------------------------------------------------------------------------------- 041 042 static final String PREFIX = "UonParser"; 043 044 /** 045 * Configuration property: Decode <js>"%xx"</js> sequences. 046 * 047 * <h5 class='section'>Property:</h5> 048 * <ul class='spaced-list'> 049 * <li><b>ID:</b> {@link org.apache.juneau.uon.UonParser#UON_decoding UON_decoding} 050 * <li><b>Name:</b> <js>"UonParser.decoding.b"</js> 051 * <li><b>Data type:</b> <jk>boolean</jk> 052 * <li><b>System property:</b> <c>UonParser.decoding</c> 053 * <li><b>Environment variable:</b> <c>UONPARSER_DECODING</c> 054 * <li><b>Default:</b> <jk>false</jk> for {@link org.apache.juneau.uon.UonParser}, <jk>true</jk> for {@link org.apache.juneau.urlencoding.UrlEncodingParser} 055 * <li><b>Session property:</b> <jk>false</jk> 056 * <li><b>Annotations:</b> 057 * <ul> 058 * <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#decoding()} 059 * </ul> 060 * <li><b>Methods:</b> 061 * <ul> 062 * <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding(boolean)} 063 * <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding()} 064 * </ul> 065 * </ul> 066 * 067 * <h5 class='section'>Description:</h5> 068 * <p> 069 * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded 070 * before being passed to this parser. 071 * 072 * <h5 class='section'>Example:</h5> 073 * <p class='bcode w800'> 074 * <jc>// Create a decoding UON parser.</jc> 075 * ReaderParser p = UonParser. 076 * .<jsm>create</jsm>() 077 * .decoding() 078 * .build(); 079 * 080 * <jc>// Same, but use property.</jc> 081 * ReaderParser p = UonParser. 082 * .<jsm>create</jsm>() 083 * .set(<jsf>UON_decoding</jsf>, <jk>true</jk>) 084 * .build(); 085 * 086 * <jc>// Produces: ["foo bar", "baz quz"].</jc> 087 * String[] foo = p.parse(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>); 088 * </p> 089 */ 090 public static final String UON_decoding = PREFIX + ".decoding.b"; 091 092 /** 093 * Configuration property: Validate end. 094 * 095 * <h5 class='section'>Property:</h5> 096 * <ul class='spaced-list'> 097 * <li><b>ID:</b> {@link org.apache.juneau.uon.UonParser#UON_validateEnd UON_validateEnd} 098 * <li><b>Name:</b> <js>"UonParser.validateEnd.b"</js> 099 * <li><b>Data type:</b> <jk>boolean</jk> 100 * <li><b>System property:</b> <c>UonParser.validateEnd</c> 101 * <li><b>Environment variable:</b> <c>UONPARSER_VALIDATEEND</c> 102 * <li><b>Default:</b> <jk>false</jk> 103 * <li><b>Session property:</b> <jk>false</jk> 104 * <li><b>Annotations:</b> 105 * <ul> 106 * <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#validateEnd()} 107 * </ul> 108 * <li><b>Methods:</b> 109 * <ul> 110 * <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#validateEnd(boolean)} 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().set(UON_decoding, true).build()); 166 } 167 } 168 169 170 //------------------------------------------------------------------------------------------------------------------- 171 // Instance 172 //------------------------------------------------------------------------------------------------------------------- 173 174 private final boolean 175 decoding, validateEnd; 176 private final Map<ClassMeta<?>,UonClassMeta> uonClassMetas = new ConcurrentHashMap<>(); 177 private final Map<BeanPropertyMeta,UonBeanPropertyMeta> uonBeanPropertyMetas = new ConcurrentHashMap<>(); 178 179 /** 180 * Constructor. 181 * 182 * @param ps 183 * The property store containing all the settings for this object. 184 */ 185 public UonParser(PropertyStore ps) { 186 this(ps, "text/uon"); 187 } 188 189 /** 190 * Constructor. 191 * 192 * @param ps 193 * The property store containing all the settings for this object. 194 * @param consumes 195 * The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*​/json"</js>). 196 */ 197 public UonParser(PropertyStore ps, String...consumes) { 198 super(ps, consumes); 199 this.decoding = getBooleanProperty(UON_decoding, false); 200 this.validateEnd = getBooleanProperty(UON_validateEnd, false); 201 } 202 203 @Override /* Context */ 204 public UonParserBuilder builder() { 205 return new UonParserBuilder(getPropertyStore()); 206 } 207 208 /** 209 * Instantiates a new clean-slate {@link UonParserBuilder} object. 210 * 211 * <p> 212 * This is equivalent to simply calling <code><jk>new</jk> UonParserBuilder()</code>. 213 * 214 * <p> 215 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 216 * the settings of the object called on. 217 * 218 * @return A new {@link UonParserBuilder} object. 219 */ 220 public static UonParserBuilder create() { 221 return new UonParserBuilder(); 222 } 223 224 /** 225 * Create a UON parser session for parsing parameter values. 226 * 227 * @return A new parser session. 228 */ 229 protected final UonParserSession createParameterSession() { 230 return new UonParserSession(this, createDefaultSessionArgs(), false); 231 } 232 233 //----------------------------------------------------------------------------------------------------------------- 234 // Entry point methods 235 //----------------------------------------------------------------------------------------------------------------- 236 237 @Override /* Parser */ 238 public UonParserSession createSession(ParserSessionArgs args) { 239 return new UonParserSession(this, args); 240 } 241 242 @Override /* HttpPartParser */ 243 public UonParserSession createSession() { 244 return createSession(null); 245 } 246 247 @Override /* HttpPartParser */ 248 public UonParserSession createPartSession(ParserSessionArgs args) { 249 return new UonParserSession(this, args); 250 } 251 252 @Override /* HttpPartParser */ 253 public UonParserSession createPartSession() { 254 return createPartSession(null); 255 } 256 257 @Override /* HttpPartParser */ 258 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException { 259 return createPartSession().parse(partType, schema, in, toType); 260 } 261 262 @Override /* HttpPartParser */ 263 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException { 264 return createPartSession().parse(partType, schema, in, toType, toTypeArgs); 265 } 266 267 @Override /* HttpPartParser */ 268 public <T> T parse(HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException { 269 return createPartSession().parse(null, schema, in, toType); 270 } 271 272 @Override /* HttpPartParser */ 273 public <T> T parse(HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException { 274 return createPartSession().parse(null, schema, in, toType, toTypeArgs); 275 } 276 277 //----------------------------------------------------------------------------------------------------------------- 278 // Extended metadata 279 //----------------------------------------------------------------------------------------------------------------- 280 281 @Override /* UonMetaProvider */ 282 public UonClassMeta getUonClassMeta(ClassMeta<?> cm) { 283 UonClassMeta m = uonClassMetas.get(cm); 284 if (m == null) { 285 m = new UonClassMeta(cm, this); 286 uonClassMetas.put(cm, m); 287 } 288 return m; 289 } 290 291 @Override /* UonMetaProvider */ 292 public UonBeanPropertyMeta getUonBeanPropertyMeta(BeanPropertyMeta bpm) { 293 if (bpm == null) 294 return UonBeanPropertyMeta.DEFAULT; 295 UonBeanPropertyMeta m = uonBeanPropertyMetas.get(bpm); 296 if (m == null) { 297 m = new UonBeanPropertyMeta(bpm.getDelegateFor(), this); 298 uonBeanPropertyMetas.put(bpm, m); 299 } 300 return m; 301 } 302 303 //----------------------------------------------------------------------------------------------------------------- 304 // Properties 305 //----------------------------------------------------------------------------------------------------------------- 306 307 /** 308 * Configuration property: Decode <js>"%xx"</js> sequences. 309 * 310 * @see #UON_decoding 311 * @return 312 * <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded 313 * before being passed to this parser. 314 */ 315 protected final boolean isDecoding() { 316 return decoding; 317 } 318 319 /** 320 * Configuration property: Validate end. 321 * 322 * @see #UON_validateEnd 323 * @return 324 * <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in 325 * the stream consists of only comments or whitespace. 326 */ 327 protected final boolean isValidateEnd() { 328 return validateEnd; 329 } 330 331 //----------------------------------------------------------------------------------------------------------------- 332 // Other methods 333 //----------------------------------------------------------------------------------------------------------------- 334 335 @Override /* Context */ 336 public ObjectMap toMap() { 337 return super.toMap() 338 .append("UonParser", new DefaultFilteringObjectMap() 339 .append("decoding", decoding) 340 .append("validateEnd", validateEnd) 341 ); 342 } 343}