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}