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&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&key=2&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<String> <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)&f2=@(c,d)"</js>, A.<jk>class</jk>); 132 * 133 * MyBean <jv>myBean2</jv> = <jv>parser2</jv>.read(<js>"f1=a&f1=b&f2=c&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&key=2&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}