001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.urlencoding;
018
019import static org.apache.juneau.commons.utils.SystemUtils.*;
020
021import java.util.*;
022import java.util.concurrent.*;
023
024import org.apache.juneau.commons.bean.*;
025import org.apache.juneau.commons.collections.*;
026import org.apache.juneau.marshall.*;
027import org.apache.juneau.marshall.uon.*;
028
029/**
030 * Parses URL-encoded text into POJO models.
031 *
032 * <h5 class='topic'>Media types</h5>
033 * <p>
034 * Handles <c>Content-Type</c> types:  <bc>application/x-www-form-urlencoded</bc>
035 *
036 * <h5 class='topic'>Description</h5>
037 * <p>
038 * Parses URL-Encoded text (e.g. <js>"foo=bar&amp;baz=bing"</js>) into POJOs.
039 *
040 * <p>
041 * Expects parameter values to be in UON notation.
042 *
043 * <p>
044 * This parser uses a state machine, which makes it very fast and efficient.
045 *
046 * <h5 class='section'>Notes:</h5><ul>
047 *    <li class='note'>This class is thread safe and reusable.
048 * </ul>
049 *
050 * <h5 class='section'>See Also:</h5><ul>
051 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/UrlEncodingSupport">URL-Encoding</a>
052 * </ul>
053 */
054@SuppressWarnings({
055   "java:S110", // Inheritance depth acceptable for this class hierarchy
056   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
057   "java:S9149" // Per-format static factories intentionally shadow the parent's.
058})
059public class UrlEncodingParser extends UonParser implements UrlEncodingMetaProvider {
060
061   /**
062    * Builder class.
063    */
064   public static class Builder extends UonParser.Builder<Builder> {
065
066      private static final Cache<HashKey,UrlEncodingParser> CACHE = Cache.of(HashKey.class, UrlEncodingParser.class).build();
067
068      boolean expandedParams;
069
070      /**
071       * Constructor, default settings.
072       */
073      protected Builder() {
074         decoding();
075         consumes("application/x-www-form-urlencoded");
076         expandedParams = env("UrlEncoding.expandedParams", false);
077      }
078
079      /**
080       * Copy constructor.
081       *
082       * @param copyFrom The builder to copy from.
083       *    <br>Cannot be <jk>null</jk>.
084       */
085      protected Builder(Builder copyFrom) {
086         super(copyFrom);
087         expandedParams = copyFrom.expandedParams;
088      }
089
090      /**
091       * Copy constructor.
092       *
093       * @param copyFrom The bean to copy from.
094       *    <br>Cannot be <jk>null</jk>.
095       */
096      protected Builder(UrlEncodingParser copyFrom) {
097         super(copyFrom);
098         expandedParams = copyFrom.expandedParams;
099      }
100
101      @Override /* Overridden from Context.Builder<?> */
102      public UrlEncodingParser build() {
103         return cache(CACHE).build(UrlEncodingParser.class);
104      }
105
106      @Override /* Overridden from Context.Builder<?> */
107      public Builder copy() {
108         return new Builder(this);
109      }
110
111      /**
112       * Serialize bean property collections/arrays as separate key/value pairs.
113       *
114       * <p>
115       * This is the parser-side equivalent of the {@link UrlEncodingSerializer.Builder#expandedParams()} setting.
116       *
117       * <p>
118       * If <jk>false</jk>, serializing the array <c>[1,2,3]</c> results in <c>?key=$a(1,2,3)</c>.
119       * <br>If <jk>true</jk>, serializing the same array results in <c>?key=1&amp;key=2&amp;key=3</c>.
120       *
121       * <h5 class='section'>Example:</h5>
122       * <p class='bjava'>
123       *    <jk>public class</jk> MyBean {
124       *       <jk>public</jk> String[] <jf>f1</jf>;
125       *       <jk>public</jk> List&lt;String&gt; <jf>f2</jf>;
126       *    }
127       *
128       *    UrlEncodingParser <jv>parser1</jv> = UrlEncodingParser.<jsf>DEFAULT</jsf>;
129       *    UrlEncodingParser <jv>parser2</jv> = UrlEncodingParser.<jsm>create</jsm>().expandedParams().build();
130       *
131       *    MyBean <jv>myBean1</jv> = <jv>parser1</jv>.read(<js>"f1=@(a,b)&amp;f2=@(c,d)"</js>, A.<jk>class</jk>);
132       *
133       *    MyBean <jv>myBean2</jv> = <jv>parser2</jv>.read(<js>"f1=a&amp;f1=b&amp;f2=c&amp;f2=d"</js>, A.<jk>class</jk>);
134       * </p>
135       *
136       * <p>
137       * This option only applies to beans.
138       *
139       * <h5 class='section'>Notes:</h5><ul>
140       *    <li class='note'>
141       *       If parsing multi-part parameters, it's highly recommended to use Collections or Lists
142       *       as bean property types instead of arrays since arrays have to be recreated from scratch every time a value
143       *       is added to it.
144       * </ul>
145       *
146       * @return This object.
147       */
148      public Builder expandedParams() {
149         return expandedParams(true);
150      }
151
152      /**
153       * Same as {@link #expandedParams()} but allows you to explicitly specify the value.
154       *
155       * @param value The value for this setting.
156       * @return This object.
157       */
158      public Builder expandedParams(boolean value) {
159         expandedParams = value;
160         return this;
161      }
162
163      @Override /* Overridden from Context.Builder<?> */
164      public HashKey hashKey() {
165         // @formatter:off
166         return HashKey.of(
167            super.hashKey(),
168            expandedParams
169         );
170         // @formatter:on
171      }
172
173
174   }
175
176   /** Reusable instance of {@link UrlEncodingParser}. */
177   public static final UrlEncodingParser DEFAULT = new UrlEncodingParser(create());
178
179   /**
180    * Creates a new builder for this object.
181    *
182    * @return A new builder.
183    */
184   public static Builder create() {
185      return new Builder();
186   }
187
188   final boolean expandedParams;
189
190   private final Map<ClassMeta<?>,UrlEncodingClassMeta> urlEncodingClassMetas = new ConcurrentHashMap<>();
191   private final Map<BeanPropertyMeta,UrlEncodingBeanPropertyMeta> urlEncodingBeanPropertyMetas = new ConcurrentHashMap<>();
192
193   /**
194    * Constructor.
195    *
196    * @param builder The builder for this object.
197    */
198   public UrlEncodingParser(Builder builder) {
199      super(builder);
200      expandedParams = builder.expandedParams;
201   }
202
203   @Override /* Overridden from Context */
204   public Builder copy() {
205      return new Builder(this);
206   }
207
208   @Override /* Overridden from Context */
209   public UrlEncodingParserSession.Builder createSession() {
210      return UrlEncodingParserSession.create(this);
211   }
212
213   @Override /* Overridden from Context */
214   public UrlEncodingParserSession getSession() { return createSession().build(); }
215
216   @Override /* Overridden from UrlEncodingMetaProvider */
217   public UrlEncodingBeanPropertyMeta getUrlEncodingBeanPropertyMeta(BeanPropertyMeta bpm) {
218      if (bpm == null)
219         return UrlEncodingBeanPropertyMeta.DEFAULT;
220      return urlEncodingBeanPropertyMetas.computeIfAbsent(bpm, k -> new UrlEncodingBeanPropertyMeta(k.getDelegateFor(), this));
221   }
222
223   @Override /* Overridden from UrlEncodingMetaProvider */
224   public UrlEncodingClassMeta getUrlEncodingClassMeta(ClassMeta<?> cm) {
225      return urlEncodingClassMetas.computeIfAbsent(cm, k -> new UrlEncodingClassMeta(k, this));
226   }
227
228   /**
229    * Parse bean property collections/arrays as separate key/value pairs.
230    *
231    * @see Builder#expandedParams()
232    * @return
233    * <jk>false</jk> if serializing the array <c>[1,2,3]</c> results in <c>?key=$a(1,2,3)</c>.
234    * <br><jk>true</jk> if serializing the same array results in <c>?key=1&amp;key=2&amp;key=3</c>.
235    */
236   protected final boolean isExpandedParams() { return expandedParams; }
237
238   @Override /* Overridden from UonParser */
239   protected FluentMap<String,Object> properties() {
240      return super.properties()
241         .a("expandedParams", expandedParams);
242   }
243}