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.rrpc;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.io.*;
023import java.lang.reflect.Method;
024
025import org.apache.juneau.http.*;
026import org.apache.juneau.http.remote.*;
027import org.apache.juneau.http.response.*;
028import org.apache.juneau.marshall.parser.*;
029import org.apache.juneau.rest.server.*;
030
031/**
032 * A session for a single HTTP request against an RRPC Java method.
033 *
034 * <h5 class='section'>Notes:</h5><ul>
035 *    <li class='warn'>This class is not thread safe.
036 * </ul>
037 *
038 * <h5 class='section'>See Also:</h5><ul>
039 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestRpc">REST/RPC</a>
040 * </ul>
041 */
042@SuppressWarnings({
043   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
044   "resource" // Resource management handled externally
045})
046public class RrpcRestOpSession extends RestOpSession {
047
048   /**
049    * Builder class.
050    */
051   public static class Builder extends RestOpSession.Builder {
052
053      private final RrpcRestOpContext ctx2;
054
055      /**
056       * Constructor.
057       *
058       * @param ctx The context object of the RRPC Java method.
059       *    <br>Cannot be <jk>null</jk>.
060       * @param session The REST session.
061       *    <br>Cannot be <jk>null</jk>.
062       */
063      public Builder(RrpcRestOpContext ctx, RestSession session) {
064         super(reqnn("ctx", ctx), reqnn("session", session));
065         this.ctx2 = ctx;
066      }
067
068      @Override
069      public RrpcRestOpSession build() {
070         return new RrpcRestOpSession(this);
071      }
072
073   }
074
075   /**
076    * Static creator.
077    *
078    * @param ctx The context of the RRPC Java Method.
079    *    <br>Cannot be <jk>null</jk>.
080    * @param session The REST session creating this session.
081    *    <br>Cannot be <jk>null</jk>.
082    * @return A new builder.
083    */
084   public static Builder create(RrpcRestOpContext ctx, RestSession session) {
085      return new Builder(reqnn("ctx", ctx), reqnn("session", session));
086
087   }
088
089   private final RrpcRestOpContext ctx;
090
091   /**
092    * Constructor.
093    *
094    * @param builder The builder for this object.
095    *    <br>Cannot be <jk>null</jk>.
096    */
097   protected RrpcRestOpSession(Builder builder) {
098      super(builder);
099      ctx = builder.ctx2;
100   }
101
102   @Override /* Overridden from RestOpSession */
103   public RrpcRestOpSession finish() {
104      super.finish();
105      return this;
106   }
107
108   @Override
109   @SuppressWarnings({
110      "java:S3776" // Cognitive complexity acceptable for RRPC request handling
111   })
112   public void run() throws Exception {
113
114      super.run();
115
116      RestRequest req = getRequest();
117      RestResponse res = getResponse();
118      RestSession session = getRestSession();
119
120      final Object o = res.hasContent() ? res.getContent(Object.class) : null;
121
122      if (eq(session.getMethod(), "GET")) {
123         res.setContent(ctx.getMeta().getMethodsByPath().keySet());
124         return;
125
126      } else if (eq(session.getMethod(), "POST")) {
127         // The method-signature path is whatever remains of the URL below this operation's matched path
128         // pattern (e.g. pattern "/calc/*" matched against "/calc/add/(int,int)" leaves "add/(int,int)"),
129         // NOT merely the last '/'-delimited segment of the full request path. A method-signature path
130         // itself contains a '/' (name/(paramTypes)), so truncating at the last slash strips the method
131         // name and leaves only the parameter-type token, which can never match a registered method path.
132         var match = session.getUrlPathMatch();
133         var pip = urlDecode(emptyIfNull(nn(match) ? match.getRemainder() : null));
134         RrpcInterfaceMethodMeta rmm = ctx.getMeta().getMethodMetaByPath(pip);
135         if (nn(rmm)) {
136            Method m = rmm.getJavaMethod();
137            try {
138               // Parse the args and invoke the method.
139               var parserMatchOpt = req.getContent().getParserMatch();
140               if (!parserMatchOpt.isPresent())
141                  throw new BadRequest("No parser match found for request content");
142               Parser p = parserMatchOpt.get().getParser();
143               Object[] args = null;
144               if (m.getGenericParameterTypes().length == 0)
145                  args = new Object[0];
146               else {
147                  try (Closeable in = p.isReaderParser() ? req.getReader() : req.getInputStream()) {
148                     args = p.readArgs(in, m.getGenericParameterTypes());
149                  }
150               }
151               res.setContent(m.invoke(o, args));
152               return;
153            } catch (BasicHttpException e) {
154               throw e;
155            } catch (Exception e) {
156               throw new InternalServerError(e);
157            }
158         }
159      }
160      throw new NotFound();
161   }
162
163   @Override /* Overridden from RestOpSession */
164   public RrpcRestOpSession status(HttpStatusLine value) {
165      super.status(value);
166      return this;
167   }
168}