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.urlencoding;
014
015import org.apache.juneau.*;
016import org.apache.juneau.parser.*;
017import org.apache.juneau.uon.*;
018import org.apache.juneau.urlencoding.annotation.*;
019
020/**
021 * Parses URL-encoded text into POJO models.
022 *
023 * <h5 class='topic'>Media types</h5>
024 *
025 * Handles <code>Content-Type</code> types:  <code><b>application/x-www-form-urlencoded</b></code>
026 *
027 * <h5 class='topic'>Description</h5>
028 *
029 * Parses URL-Encoded text (e.g. <js>"foo=bar&amp;baz=bing"</js>) into POJOs.
030 *
031 * <p>
032 * Expects parameter values to be in UON notation.
033 *
034 * <p>
035 * This parser uses a state machine, which makes it very fast and efficient.
036 */
037public class UrlEncodingParser extends UonParser {
038
039   //-------------------------------------------------------------------------------------------------------------------
040   // Configurable properties
041   //-------------------------------------------------------------------------------------------------------------------
042
043   private static final String PREFIX = "UrlEncodingParser.";
044
045   /**
046    * Configuration property:  Parser bean property collections/arrays as separate key/value pairs.
047    *
048    * <h5 class='section'>Property:</h5>
049    * <ul>
050    *    <li><b>Name:</b>  <js>"UrlEncodingParser.expandedParams.b"</js>
051    *    <li><b>Data type:</b>  <code>Boolean</code>
052    *    <li><b>Default:</b>  <jk>false</jk>
053    *    <li><b>Session property:</b>  <jk>false</jk>
054    *    <li><b>Methods:</b>
055    *       <ul>
056    *          <li class='jm'>{@link UrlEncodingParserBuilder#expandedParams(boolean)}
057    *          <li class='jm'>{@link UrlEncodingParserBuilder#expandedParams()}
058    *          <li class='ja'>{@link UrlEncoding#expandedParams()}
059    *       </ul>
060    * </ul>
061    *
062    * <h5 class='section'>Description:</h5>
063    * <p>
064    * This is the parser-side equivalent of the {@link #URLENC_expandedParams} setting.
065    *
066    * <p>
067    * If <jk>false</jk>, serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>.
068    * <br>If <jk>true</jk>, serializing the same array results in <code>?key=1&amp;key=2&amp;key=3</code>.
069    *
070    * <h5 class='section'>Example:</h5>
071    * <p class='bcode w800'>
072    *    <jk>public class</jk> A {
073    *       <jk>public</jk> String[] f1;
074    *       <jk>public</jk> List&lt;String&gt; f2;
075    *    }
076    *
077    *    UrlEncodingParser p1 = UrlEncodingParser.<jsf>DEFAULT</jsf>;
078    *    UrlEncodingParser p2 = UrlEncodingParser.<jsm>create</jsm>().expandedParams().build();
079    *
080    *    A a1 = p1.parse(<js>"f1=@(a,b)&amp;f2=@(c,d)"</js>, A.<jk>class</jk>);
081    *
082    *    A a2 = p2.parse(<js>"f1=a&amp;f1=b&amp;f2=c&amp;f2=d"</js>, A.<jk>class</jk>);
083    * </p>
084    *
085    * <p>
086    * This option only applies to beans.
087    *
088    * <h5 class='section'>Notes:</h5>
089    * <ul class='spaced-list'>
090    *    <li>
091    *       If parsing multi-part parameters, it's highly recommended to use Collections or Lists
092    *       as bean property types instead of arrays since arrays have to be recreated from scratch every time a value
093    *       is added to it.
094    * </ul>
095    */
096   public static final String URLENC_expandedParams = PREFIX + "expandedParams.b";
097
098
099   //-------------------------------------------------------------------------------------------------------------------
100   // Predefined instances
101   //-------------------------------------------------------------------------------------------------------------------
102
103   /** Reusable instance of {@link UrlEncodingParser}. */
104   public static final UrlEncodingParser DEFAULT = new UrlEncodingParser(PropertyStore.DEFAULT);
105
106
107   //-------------------------------------------------------------------------------------------------------------------
108   // Instance
109   //-------------------------------------------------------------------------------------------------------------------
110
111   private final boolean expandedParams;
112
113   /**
114    * Constructor.
115    *
116    * @param ps The property store containing all the settings for this object.
117    */
118   public UrlEncodingParser(PropertyStore ps) {
119      super(
120         ps.builder()
121            .set(UON_decoding, true)
122            .build(),
123         "application/x-www-form-urlencoded"
124      );
125      expandedParams = getBooleanProperty(URLENC_expandedParams, false);
126   }
127
128   @Override /* Context */
129   public UrlEncodingParserBuilder builder() {
130      return new UrlEncodingParserBuilder(getPropertyStore());
131   }
132
133   /**
134    * Instantiates a new clean-slate {@link UrlEncodingParserBuilder} object.
135    *
136    * <p>
137    * This is equivalent to simply calling <code><jk>new</jk> UrlEncodingParserBuilder()</code>.
138    *
139    * <p>
140    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
141    * the settings of the object called on.
142    *
143    * @return A new {@link UrlEncodingParserBuilder} object.
144    */
145   public static UrlEncodingParserBuilder create() {
146      return new UrlEncodingParserBuilder();
147   }
148
149
150   //-----------------------------------------------------------------------------------------------------------------
151   // Entry point methods
152   //-----------------------------------------------------------------------------------------------------------------
153
154   @Override /* Parser */
155   public UrlEncodingParserSession createSession(ParserSessionArgs args) {
156      return new UrlEncodingParserSession(this, args);
157   }
158
159   //-----------------------------------------------------------------------------------------------------------------
160   // Properties
161   //-----------------------------------------------------------------------------------------------------------------
162
163   /**
164    * Configuration property:  Parser bean property collections/arrays as separate key/value pairs.
165    *
166    * @see #URLENC_expandedParams
167    * @return
168    * <jk>false</jk> if serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>.
169    * <br><jk>true</jk> if serializing the same array results in <code>?key=1&amp;key=2&amp;key=3</code>.
170    */
171   protected final boolean isExpandedParams() {
172      return expandedParams;
173   }
174
175   @Override /* Context */
176   public ObjectMap asMap() {
177      return super.asMap()
178         .append("UrlEncodingParser", new ObjectMap()
179            .append("expandedParams", expandedParams)
180         );
181   }
182}