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.rest.arg; 018 019import static org.apache.juneau.common.utils.StringUtils.*; 020import static org.apache.juneau.common.utils.Utils.*; 021import static org.apache.juneau.http.annotation.HeaderAnnotation.*; 022 023import java.util.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.annotation.*; 027import org.apache.juneau.collections.*; 028import org.apache.juneau.http.annotation.*; 029import org.apache.juneau.http.header.*; 030import org.apache.juneau.httppart.*; 031import org.apache.juneau.internal.*; 032import org.apache.juneau.reflect.*; 033import org.apache.juneau.rest.*; 034import org.apache.juneau.rest.annotation.*; 035import org.apache.juneau.rest.httppart.*; 036 037/** 038 * Resolves method parameters and parameter types annotated with {@link Header} on {@link RestOp}-annotated Java methods. 039 * 040 * <p> 041 * This includes any of the following predefined request header types: 042 * 043 * <ul class='javatree condensed'> 044 * <li class='jc'>{@link Accept} 045 * <li class='jc'>{@link AcceptCharset} 046 * <li class='jc'>{@link AcceptEncoding} 047 * <li class='jc'>{@link AcceptLanguage} 048 * <li class='jc'>{@link AcceptRanges} 049 * <li class='jc'>{@link Authorization} 050 * <li class='jc'>{@link CacheControl} 051 * <li class='jc'>{@link ClientVersion} 052 * <li class='jc'>{@link Connection} 053 * <li class='jc'>{@link ContentDisposition} 054 * <li class='jc'>{@link ContentEncoding} 055 * <li class='jc'>{@link ContentLength} 056 * <li class='jc'>{@link ContentType} 057 * <li class='jc'>{@link org.apache.juneau.http.header.Date} 058 * <li class='jc'>{@link Debug} 059 * <li class='jc'>{@link Expect} 060 * <li class='jc'>{@link Forwarded} 061 * <li class='jc'>{@link From} 062 * <li class='jc'>{@link Host} 063 * <li class='jc'>{@link IfMatch} 064 * <li class='jc'>{@link IfModifiedSince} 065 * <li class='jc'>{@link IfNoneMatch} 066 * <li class='jc'>{@link IfRange} 067 * <li class='jc'>{@link IfUnmodifiedSince} 068 * <li class='jc'>{@link MaxForwards} 069 * <li class='jc'>{@link NoTrace} 070 * <li class='jc'>{@link Origin} 071 * <li class='jc'>{@link Pragma} 072 * <li class='jc'>{@link ProxyAuthorization} 073 * <li class='jc'>{@link Range} 074 * <li class='jc'>{@link Referer} 075 * <li class='jc'>{@link TE} 076 * <li class='jc'>{@link Thrown} 077 * <li class='jc'>{@link Upgrade} 078 * <li class='jc'>{@link UserAgent} 079 * <li class='jc'>{@link Warning} 080 * </ul> 081 * 082 * <p> 083 * The parameter value is resolved using: 084 * <p class='bjava'> 085 * <jv>opSession</jv> 086 * .{@link RestOpSession#getRequest() getRequest}() 087 * .{@link RestRequest#getHeaders() getHeaders}(); 088 * </p> 089 * 090 * <p> 091 * {@link HttpPartSchema schema} is derived from the {@link Header} annotation. 092 * 093 * <p> 094 * If the {@link Schema#collectionFormat()} value is {@link HttpPartCollectionFormat#MULTI}, then the data type can be a {@link Collection} or array. 095 * 096 * <h5 class='section'>See Also:</h5><ul> 097 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JavaMethodParameters">Java Method Parameters</a> 098 * </ul> 099 */ 100public class HeaderArg implements RestOpArg { 101 private final HttpPartParser partParser; 102 private final HttpPartSchema schema; 103 private final boolean multi; 104 private final String name, def; 105 private final ClassInfo type; 106 107 /** 108 * Static creator. 109 * 110 * @param paramInfo The Java method parameter being resolved. 111 * @param annotations The annotations to apply to any new part parsers. 112 * @return A new {@link HeaderArg}, or <jk>null</jk> if the parameter is not annotated with {@link Header}. 113 */ 114 public static HeaderArg create(ParamInfo paramInfo, AnnotationWorkList annotations) { 115 if ((!paramInfo.getParameterType().is(Value.class)) && (paramInfo.hasAnnotation(Header.class) || paramInfo.getParameterType().hasAnnotation(Header.class))) 116 return new HeaderArg(paramInfo, annotations); 117 return null; 118 } 119 120 /** 121 * Constructor. 122 * 123 * @param pi The Java method parameter being resolved. 124 * @param annotations The annotations to apply to any new part parsers. 125 */ 126 protected HeaderArg(ParamInfo pi, AnnotationWorkList annotations) { 127 this.name = findName(pi).orElseThrow(() -> new ArgException(pi, "@Header used without name or value")); 128 this.def = findDef(pi).orElse(null); 129 this.type = pi.getParameterType(); 130 this.schema = HttpPartSchema.create(Header.class, pi); 131 Class<? extends HttpPartParser> pp = schema.getParser(); 132 this.partParser = pp != null ? HttpPartParser.creator().type(pp).apply(annotations).create() : null; 133 this.multi = schema.getCollectionFormat() == HttpPartCollectionFormat.MULTI; 134 135 if (multi && ! type.isCollectionOrArray()) 136 throw new ArgException(pi, "Use of multipart flag on @Header parameter that is not an array or Collection"); 137 } 138 139 @SuppressWarnings({ "rawtypes", "unchecked" }) 140 @Override /* RestOpArg */ 141 public Object resolve(RestOpSession opSession) throws Exception { 142 RestRequest req = opSession.getRequest(); 143 HttpPartParserSession ps = partParser == null ? req.getPartParserSession() : partParser.getPartSession(); 144 RequestHeaders rh = req.getHeaders(); 145 BeanSession bs = req.getBeanSession(); 146 ClassMeta<?> cm = bs.getClassMeta(type.innerType()); 147 148 if (multi) { 149 Collection c = cm.isArray() ? list() : (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new JsonList()); 150 rh.stream(name).map(x -> x.parser(ps).schema(schema).as(cm.getElementType()).orElse(null)).forEach(x -> c.add(x)); 151 return cm.isArray() ? ArrayUtils.toArray(c, cm.getElementType().getInnerClass()) : c; 152 } 153 154 if (cm.isMapOrBean() && isOneOf(name, "*", "")) { 155 JsonMap m = new JsonMap(); 156 rh.forEach(x -> m.put(x.getName(), x.parser(ps).schema(schema == null ? null : schema.getProperty(x.getName())).as(cm.getValueType()).orElse(null))); 157 return req.getBeanSession().convertToType(m, cm); 158 } 159 160 return rh.getLast(name).parser(ps).schema(schema).def(def).as(type.innerType()).orElse(null); 161 } 162}