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.http; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022import java.util.function.*; 023 024import org.apache.juneau.commons.httppart.*; 025import org.apache.juneau.commons.lang.*; 026import org.apache.juneau.commons.reflect.*; 027import org.apache.juneau.http.part.*; 028import org.apache.juneau.marshall.*; 029 030/** 031 * Static factory methods for creating HTTP parts (query parameters, form fields, path variables). 032 * 033 * <p> 034 * Import statically for clean DSL-style usage: 035 * <p class='bjava'> 036 * import static HttpParts.*; 037 * 038 * PartList <jv>form</jv> = PartList.<jsm>of</jsm>( 039 * part(<js>"username"</js>, <js>"alice"</js>), 040 * part(<js>"password"</js>, <jv>passwordSupplier</jv>) 041 * ); 042 * </p> 043 * 044 * <h5 class='section'>See Also:</h5><ul> 045 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a> 046 * </ul> 047 * 048 * @since 9.2.1 049 */ 050public final class HttpParts { 051 052 private HttpParts() {} 053 054 /** 055 * Creates an {@link HttpPart} with an eager string value. 056 * 057 * @param name The part name. Must not be <jk>null</jk>. 058 * @param value The part value. May be <jk>null</jk>. 059 * @return A new part. Never <jk>null</jk>. 060 */ 061 public static HttpPart part(String name, String value) { 062 return HttpPartBean.of(name, value); 063 } 064 065 /** 066 * Creates an {@link HttpPart} with a lazy value supplier. 067 * 068 * @param name The part name. Must not be <jk>null</jk>. 069 * @param valueSupplier Supplier for the value. Must not be <jk>null</jk>. 070 * @return A new part. Never <jk>null</jk>. 071 */ 072 public static HttpPart part(String name, Supplier<String> valueSupplier) { 073 return HttpPartBean.of(name, valueSupplier); 074 } 075 076 /** 077 * Creates a {@link PartList} from the given parts. 078 * 079 * @param parts The parts. Must not be <jk>null</jk>. 080 * @return A new list. Never <jk>null</jk>. 081 */ 082 public static PartList partList(HttpPart... parts) { 083 return PartList.of(parts); 084 } 085 086 /** 087 * Creates a {@link PartList} from alternating name/value string pairs. 088 * 089 * @param pairs Alternating name/value strings. Must not be <jk>null</jk>. Length must be even. 090 * @return A new list. Never <jk>null</jk>. 091 */ 092 public static PartList partListOfPairs(String... pairs) { 093 return PartList.ofPairs(pairs); 094 } 095 096 private static final Function<ClassMeta<?>,ConstructorInfo> CONSTRUCTOR_FUNCTION = 097 x -> x.getPublicConstructor(y -> y.hasParameterTypes(String.class)) 098 .orElseGet(() -> x.getPublicConstructor(y -> y.hasParameterTypes(String.class, String.class)).orElse(null)); 099 100 /** 101 * Returns the {@code (String)} or {@code (String,String)} public constructor for the specified type. 102 * 103 * <p> 104 * Used by part-parsing logic to instantiate typed parts from a wire string. 105 * 106 * @param type The type to find the constructor on. Must not be <jk>null</jk>. 107 * @return The constructor wrapped in an {@link Optional}; empty if none is found. 108 */ 109 public static Optional<ConstructorInfo> getConstructor(ClassMeta<?> type) { 110 return type.getProperty("HttpPart.Constructor", CONSTRUCTOR_FUNCTION); 111 } 112 113 /** 114 * Returns <jk>true</jk> if the specified type is a transport-neutral part type for the given 115 * {@link HttpPartType}. 116 * 117 * <p> 118 * The check is whether the type is assignable to {@link HttpHeader} (for {@code HEADER}) or 119 * {@link HttpPart} (for {@code PATH}, {@code QUERY}, {@code FORMDATA}). 120 * 121 * @param partType The part type. Must not be <jk>null</jk>. 122 * @param type The type to check. Must not be <jk>null</jk>. 123 * @return <jk>true</jk> if the type is a part type for the given category. 124 */ 125 public static boolean isHttpPart(HttpPartType partType, ClassMeta<?> type) { 126 return switch (partType) { 127 case PATH, QUERY, FORMDATA -> type.getProperty("HttpPart.isHttpPart", x -> x.isAssignableTo(HttpPart.class)).orElse(false); 128 case HEADER -> type.getProperty("HttpPart.isHttpHeader", x -> x.isAssignableTo(HttpHeader.class)).orElse(false); 129 default -> false; 130 }; 131 } 132 133 private static final Function<ClassMeta<?>,String> HEADER_NAME_FUNCTION = x -> { 134 var n = Holder.<String>empty(); 135 x.forEachAnnotation(Header.class, y -> ine(y.value()), y -> n.set(y.value())); 136 x.forEachAnnotation(Header.class, y -> ine(y.name()), y -> n.set(y.name())); 137 if (n.isEmpty()) 138 n.set(readPublicStaticStringField(x.inner(), "NAME")); 139 return n.orElse(null); 140 }; 141 142 private static final Function<ClassMeta<?>,String> QUERY_NAME_FUNCTION = x -> { 143 var n = Holder.<String>empty(); 144 x.forEachAnnotation(Query.class, y -> ine(y.value()), y -> n.set(y.value())); 145 x.forEachAnnotation(Query.class, y -> ine(y.name()), y -> n.set(y.name())); 146 if (n.isEmpty()) 147 n.set(readPublicStaticStringField(x.inner(), "NAME")); 148 return n.orElse(null); 149 }; 150 151 private static final Function<ClassMeta<?>,String> FORMDATA_NAME_FUNCTION = x -> { 152 var n = Holder.<String>empty(); 153 x.forEachAnnotation(FormData.class, y -> ine(y.value()), y -> n.set(y.value())); 154 x.forEachAnnotation(FormData.class, y -> ine(y.name()), y -> n.set(y.name())); 155 if (n.isEmpty()) 156 n.set(readPublicStaticStringField(x.inner(), "NAME")); 157 return n.orElse(null); 158 }; 159 160 private static final Function<ClassMeta<?>,String> PATH_NAME_FUNCTION = x -> { 161 var n = Holder.<String>empty(); 162 x.forEachAnnotation(Path.class, y -> ine(y.value()), y -> n.set(y.value())); 163 x.forEachAnnotation(Path.class, y -> ine(y.name()), y -> n.set(y.name())); 164 if (n.isEmpty()) 165 n.set(readPublicStaticStringField(x.inner(), "NAME")); 166 return n.orElse(null); 167 }; 168 169 private static String readPublicStaticStringField(Class<?> c, String fieldName) { 170 try { 171 var f = c.getField(fieldName); 172 if (java.lang.reflect.Modifier.isStatic(f.getModifiers()) && f.getType() == String.class) { 173 var v = f.get(null); 174 return v == null ? null : v.toString(); 175 } 176 } catch (@SuppressWarnings("unused") ReflectiveOperationException ignored) { 177 // Field does not exist or is not accessible. 178 } 179 return null; 180 } 181 182 /** 183 * Returns the name of the specified part type by reading the {@code @Header}, {@code @Query}, 184 * {@code @FormData}, or {@code @Path} annotation on the class. 185 * 186 * @param partType The part type. Must not be <jk>null</jk>. 187 * @param type The type to check. Must not be <jk>null</jk>. 188 * @return The part name, wrapped in an {@link Optional}. 189 */ 190 public static Optional<String> getName(HttpPartType partType, ClassMeta<?> type) { 191 return switch (partType) { 192 case FORMDATA -> type.getProperty("HttpPart.formData.name", FORMDATA_NAME_FUNCTION); 193 case HEADER -> type.getProperty("HttpPart.header.name", HEADER_NAME_FUNCTION); 194 case PATH -> type.getProperty("HttpPart.path.name", PATH_NAME_FUNCTION); 195 case QUERY -> type.getProperty("HttpPart.query.name", QUERY_NAME_FUNCTION); 196 default -> oe(); 197 }; 198 } 199}