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