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.server.arg; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021 022import java.lang.reflect.*; 023import java.util.*; 024 025import org.apache.juneau.commons.httppart.*; 026import org.apache.juneau.commons.lang.*; 027import org.apache.juneau.commons.reflect.*; 028import org.apache.juneau.http.*; 029import org.apache.juneau.http.header.*; 030import org.apache.juneau.marshall.*; 031import org.apache.juneau.marshall.httppart.*; 032import org.apache.juneau.rest.server.*; 033import org.apache.juneau.rest.server.httppart.*; 034 035/** 036 * Resolves method parameters annotated with {@link Header} of type {@link org.apache.juneau.commons.inject.Value} representing response headers 037 * on {@link RestOp}-annotated Java methods. 038 * 039 * <p> 040 * The parameter value must be of type {@link org.apache.juneau.commons.inject.Value} that accepts a value that is then set via: 041 * <p class='bjava'> 042 * <jv>opSession</jv> 043 * .{@link RestOpSession#getResponse() getResponse}() 044 * .{@link RestResponse#setHeader(String,String) setHeader}(<jv>name</jv>,<jv>value</jv>); 045 * </p> 046 * 047 * <h5 class='section'>See Also:</h5><ul> 048 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JavaMethodParameters">Java Method Parameters</a> 049 * </ul> 050 */ 051public class ResponseHeaderArg implements RestOpArg { 052 053 private static final AnnotationProvider AP = AnnotationProvider.INSTANCE; 054 055 /** 056 * Static creator. 057 * 058 * @param paramInfo The Java method parameter being resolved. Must not be <jk>null</jk>. 059 * @param annotations The annotations to apply to any new part parsers. 060 * @return A new {@link ResponseHeaderArg}, or <jk>null</jk> if the parameter is not annotated with {@link Header}. 061 */ 062 public static ResponseHeaderArg create(ParameterInfo paramInfo, AnnotationWorkList annotations) { 063 if (paramInfo.getParameterType().is(Holder.class) && AP.has(Header.class, paramInfo)) 064 return new ResponseHeaderArg(paramInfo, annotations); 065 return null; 066 } 067 068 private static Optional<String> findName(ParameterInfo pi) { 069 // @formatter:off 070 return AP.find(Header.class, pi) 071 .stream() 072 .map(AnnotationInfo::inner) 073 .filter(x -> isAnyNotBlank(x.value(), x.name())) 074 .findFirst() 075 .map(x -> firstNonBlank(x.name(), x.value())); 076 // @formatter:on 077 } 078 079 final ResponsePartMeta meta; 080 final String name; 081 082 private final Type type; 083 084 /** 085 * Constructor. 086 * 087 * @param pi The Java method parameter being resolved. Must not be <jk>null</jk>. 088 * @param annotations The annotations to apply to any new part parsers. 089 */ 090 protected ResponseHeaderArg(ParameterInfo pi, AnnotationWorkList annotations) { 091 this.name = findName(pi).orElseThrow(() -> new ArgException(pi, "@Header used without name or value")); 092 this.type = pi.getParameterType().innerType(); 093 var schema = HttpPartSchema.create(Header.class, pi); 094 095 @SuppressWarnings({ 096 "unchecked" // Type erasure on reflective/generic cast; element type is verified at call site 097 }) 098 var ps = (Class<? extends HttpPartSerializer>)schema.getSerializer(); 099 this.meta = new ResponsePartMeta(HttpPartType.HEADER, schema, nn(ps) ? HttpPartSerializer.creator().type(ps).apply(annotations).create() : null); 100 101 Class<?> c; 102 if (type instanceof Class) { 103 c = (Class<?>)type; 104 } else if (type instanceof ParameterizedType) { 105 c = (Class<?>)((ParameterizedType)type).getRawType(); 106 } else { 107 c = null; 108 } 109 if (c != Holder.class) 110 throw new ArgException(pi, "Type must be Holder<?> on parameter annotated with @Header annotation"); 111 } 112 113 @Override /* Overridden from RestOpArg */ 114 public Object resolve(RestOpSession opSession) throws Exception { 115 var v = new Holder<>(); 116 v.listener(o -> { 117 var req = opSession.getRequest(); 118 var res = opSession.getResponse(); 119 var rpm = req.getOpContext().getResponseHeaderMeta(o); 120 if (rpm == null) 121 rpm = meta; 122 var pss = rpm.getSerializer() == null ? req.getPartSerializerSession() : rpm.getSerializer().getPartSession(); 123 var s = pss.write(HttpPartType.HEADER, rpm.getSchema(), o); 124 res.setHeader(HttpStringHeader.of(name, s)); 125 }); 126 return v; 127 } 128}