001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest.arg; 014 015import static org.apache.juneau.http.annotation.PathAnnotation.*; 016import java.lang.reflect.*; 017 018import org.apache.juneau.*; 019import org.apache.juneau.collections.*; 020import org.apache.juneau.common.internal.*; 021import org.apache.juneau.http.annotation.*; 022import org.apache.juneau.httppart.*; 023import org.apache.juneau.reflect.*; 024import org.apache.juneau.rest.*; 025import org.apache.juneau.rest.annotation.*; 026import org.apache.juneau.rest.httppart.*; 027import org.apache.juneau.rest.util.*; 028 029/** 030 * Resolves method parameters and parameter types annotated with {@link Path} on {@link RestOp}-annotated Java methods. 031 * 032 * <p> 033 * The parameter value is resolved using: 034 * <p class='bjava'> 035 * <jv>opSession</jv> 036 * .{@link RestOpSession#getRequest() getRequest}() 037 * .{@link RestRequest#getPathParams() getPathParams}() 038 * .{@link RequestPathParams#get(String) get}(<jv>name</jv>) 039 * .{@link RequestPathParam#as(Class) as}(<jv>type</jv>); 040 * </p> 041 * 042 * <p> 043 * {@link HttpPartSchema schema} is derived from the {@link Path} annotation. 044 * 045 * <h5 class='section'>See Also:</h5><ul> 046 * <li class='link'><a class="doclink" href="../../../../../index.html#jrs.JavaMethodParameters">Java Method Parameters</a> 047 * </ul> 048 */ 049public class PathArg implements RestOpArg { 050 private final HttpPartParser partParser; 051 private final HttpPartSchema schema; 052 private final String name, def; 053 private final Type type; 054 055 /** 056 * Static creator. 057 * 058 * @param paramInfo The Java method parameter being resolved. 059 * @param annotations The annotations to apply to any new part parsers. 060 * @param pathMatcher Path matcher for the specified method. 061 * @return A new {@link PathArg}, or <jk>null</jk> if the parameter is not annotated with {@link Path}. 062 */ 063 public static PathArg create(ParamInfo paramInfo, AnnotationWorkList annotations, UrlPathMatcher pathMatcher) { 064 if (paramInfo.hasAnnotation(Path.class) || paramInfo.getParameterType().hasAnnotation(Path.class)) 065 return new PathArg(paramInfo, annotations, pathMatcher); 066 return null; 067 } 068 069 /** 070 * Constructor. 071 * 072 * @param paramInfo The Java method parameter being resolved. 073 * @param annotations The annotations to apply to any new part parsers. 074 * @param pathMatcher Path matcher for the specified method. 075 */ 076 protected PathArg(ParamInfo paramInfo, AnnotationWorkList annotations, UrlPathMatcher pathMatcher) { 077 this.name = getName(paramInfo, pathMatcher); 078 this.def = findDef(paramInfo).orElse(null); 079 this.type = paramInfo.getParameterType().innerType(); 080 this.schema = HttpPartSchema.create(Path.class, paramInfo); 081 Class<? extends HttpPartParser> pp = schema.getParser(); 082 this.partParser = pp != null ? HttpPartParser.creator().type(pp).apply(annotations).create() : null; 083 } 084 085 private String getName(ParamInfo pi, UrlPathMatcher pathMatcher) { 086 String p = findName(pi).orElse(null); 087 if (p != null) 088 return p; 089 if (pathMatcher != null) { 090 int idx = 0; 091 int i = pi.getIndex(); 092 MethodInfo mi = pi.getMethod(); 093 094 for (int j = 0; j < i; j++) 095 if (mi.getParam(i).getAnnotation(Path.class) != null) 096 idx++; 097 098 String[] vars = pathMatcher.getVars(); 099 if (vars.length <= idx) 100 throw new ArgException(pi, "Number of attribute parameters exceeds the number of URL pattern variables"); 101 102 // Check for {#} variables. 103 String idxs = String.valueOf(idx); 104 for (String var : vars) 105 if (StringUtils.isNumeric(var) && var.equals(idxs)) 106 return var; 107 108 return pathMatcher.getVars()[idx]; 109 } 110 throw new ArgException(pi, "@Path used without name or value"); 111 } 112 113 @Override /* RestOpArg */ 114 public Object resolve(RestOpSession opSession) throws Exception { 115 RestRequest req = opSession.getRequest(); 116 if (name.equals("*")) { 117 JsonMap m = new JsonMap(); 118 req.getPathParams().stream().forEach(x -> m.put(x.getName(), x.getValue())); 119 return req.getBeanSession().convertToType(m, type); 120 } 121 HttpPartParserSession ps = partParser == null ? req.getPartParserSession() : partParser.getPartSession(); 122 return req.getPathParams().get(name).parser(ps).schema(schema).def(def).as(type).orElse(null); 123 } 124}