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