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.*;
020
021import java.lang.reflect.*;
022import java.util.*;
023
024import org.apache.juneau.commons.reflect.*;
025import org.apache.juneau.http.*;
026import org.apache.juneau.marshall.*;
027import org.apache.juneau.marshall.httppart.*;
028import org.apache.juneau.rest.server.*;
029import org.apache.juneau.rest.server.httppart.*;
030
031/**
032 * Resolves method parameters annotated with {@link PathRemainder} on {@link RestOp}-annotated Java methods.
033 *
034 * <p>
035 * This is a specialized version of {@link PathArg} for the path remainder (the part matched by {@code /*}).
036 * It's functionally equivalent to using {@code @Path("/*")}, but provides a more intuitive annotation name.
037 *
038 * <p>
039 * The parameter value is resolved using:
040 * <p class='bjava'>
041 *    <jv>opSession</jv>
042 *       .{@link RestOpSession#getRequest() getRequest}()
043 *       .{@link RestRequest#getPathParams() getPathParams}()
044 *       .{@link RequestPathParamList#get(String) get}(<js>"/*"</js>)
045 *       .{@link RequestPathParam#as(Class) as}(<jv>type</jv>);
046 * </p>
047 *
048 * <p>
049 * {@link HttpPartSchema schema} is derived from the {@link PathRemainder} annotation.
050 *
051 * <h5 class='section'>See Also:</h5><ul>
052 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JavaMethodParameters">Java Method Parameters</a>
053 *    <li class='ja'>{@link PathRemainder}
054 *    <li class='jc'>{@link PathArg}
055 * </ul>
056 *
057 * @since 9.2.0
058 */
059public class PathRemainderArg implements RestOpArg {
060
061   private static final AnnotationProvider AP = AnnotationProvider.INSTANCE;
062
063   /**
064    * Static creator.
065    *
066    * @param paramInfo The Java method parameter being resolved. Must not be <jk>null</jk>.
067    * @param annotations The annotations to apply to any new part parsers.
068    * @return A new {@link PathRemainderArg}, or <jk>null</jk> if the parameter is not annotated with {@link PathRemainder}.
069    */
070   public static PathRemainderArg create(ParameterInfo paramInfo, AnnotationWorkList annotations) {
071      if (AP.has(PathRemainder.class, paramInfo))
072         return new PathRemainderArg(paramInfo, annotations);
073      return null;
074   }
075
076   private static Optional<String> findDef(ParameterInfo pi) {
077      // @formatter:off
078      return AP.find(PathRemainder.class, pi)
079         .stream()
080         .map(AnnotationInfo::inner)
081         .filter(x -> ine(x.def()))
082         .findFirst()
083         .map(PathRemainder::def);
084      // @formatter:on
085   }
086
087   private final HttpPartParser partParser;
088   private final HttpPartSchema schema;
089   private final String def;
090
091   private final Type type;
092
093   /**
094    * Constructor.
095    *
096    * @param paramInfo The Java method parameter being resolved. Must not be <jk>null</jk>.
097    * @param annotations The annotations to apply to any new part parsers.
098    */
099   protected PathRemainderArg(ParameterInfo paramInfo, AnnotationWorkList annotations) {
100      this.def = findDef(paramInfo).orElse(null);
101      this.type = paramInfo.getParameterType().innerType();
102      this.schema = HttpPartSchema.create(PathRemainder.class, paramInfo);
103      @SuppressWarnings({
104         "unchecked" // Type erasure on reflective/generic cast; element type is verified at call site
105      })
106      var pp = (Class<? extends HttpPartParser>)schema.getParser();
107      this.partParser = nn(pp) ? HttpPartParser.creator().type(pp).apply(annotations).create() : null;
108   }
109
110   @Override /* Overridden from RestOpArg */
111   public Object resolve(RestOpSession opSession) throws Exception {
112      var req = opSession.getRequest();
113      var ps = partParser == null ? req.getPartParserSession() : partParser.getPartSession();
114      // The path remainder is stored under the name "/*"
115      return req.getPathParams().get("/*").parser(ps).schema(schema).def(def).as(type).orElse(null);
116   }
117}