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.remote;
014
015import static org.apache.juneau.common.internal.StringUtils.*;
016import static org.apache.juneau.dto.html5.HtmlBuilder.*;
017import static org.apache.juneau.http.HttpMethod.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
019
020import java.io.*;
021import java.lang.reflect.*;
022import java.util.*;
023import java.util.Map;
024import java.util.concurrent.*;
025
026import org.apache.juneau.annotation.*;
027import org.apache.juneau.dto.*;
028import org.apache.juneau.dto.html5.*;
029import org.apache.juneau.html.annotation.*;
030import org.apache.juneau.http.annotation.*;
031import org.apache.juneau.http.annotation.Header;
032import org.apache.juneau.parser.*;
033import org.apache.juneau.reflect.*;
034import org.apache.juneau.rest.annotation.*;
035import org.apache.juneau.rest.servlet.*;
036import org.apache.juneau.http.header.*;
037import org.apache.juneau.http.remote.*;
038import org.apache.juneau.http.response.*;
039
040/**
041 * Abstract class for defining Remote Interface Services.
042 *
043 * <p>
044 * Remote Interface Services are POJOs whose methods can be invoked remotely through proxy interfaces.
045 *
046 * <p>
047 * To implement a remote interface service, developers must simply subclass from this class and implement the
048 * {@link #getServiceMap()} method that maps java interfaces to POJO instances.
049 *
050 * <h5 class='section'>See Also:</h5><ul>
051 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.RestRpc">REST/RPC</a>
052 * </ul>
053 */
054@SuppressWarnings({"serial","javadoc"})
055public abstract class RrpcServlet extends BasicRestServlet {
056
057   private final Map<String,RrpcInterfaceMeta> serviceMap = new ConcurrentHashMap<>();
058
059   //-----------------------------------------------------------------------------------------------------------------
060   // Abstract methods
061   //-----------------------------------------------------------------------------------------------------------------
062
063   /**
064    * Returns the list of interfaces to their implementation objects.
065    *
066    * <p>
067    * This class is called often and not cached, so any caching should occur in the subclass if necessary.
068    *
069    * @return The service map.
070    * @throws Exception Any exception.
071    */
072   protected abstract Map<Class<?>,Object> getServiceMap() throws Exception;
073
074   //-----------------------------------------------------------------------------------------------------------------
075   // REST methods
076   //-----------------------------------------------------------------------------------------------------------------
077
078   @RestGet(
079      path="/",
080      summary="List of available remote interfaces",
081      description="Shows a list of the interfaces registered with this remote interface servlet."
082   )
083   public List<LinkString> getInterfaces() throws Exception {
084      List<LinkString> l = new LinkedList<>();
085      for (Class<?> c : getServiceMap().keySet())
086         l.add(new LinkString(c.getName(), "servlet:/{0}", urlEncode(c.getName())));
087      return l;
088   }
089
090   @RestGet(
091      path="/{javaInterface}",
092      summary="List of available methods on interface",
093      description="Shows a list of all the exposed methods on an interface."
094   )
095   @HtmlDocConfig(
096      nav="<h5>Interface:  $RP{javaInterface}</h5>"
097   )
098   public Collection<LinkString> listMethods(
099         @Path("javaInterface") @Schema(description="Java interface name") String javaInterface
100      ) throws Exception {
101
102      List<LinkString> l = list();
103      for (String s : getMethods(javaInterface).keySet())
104         l.add(new LinkString(s, "servlet:/{0}/{1}", urlEncode(javaInterface), urlEncode(s)));
105      return l;
106   }
107
108   @RestGet(
109      path="/{javaInterface}/{javaMethod}",
110      summary="Form entry for interface method call",
111      description="Shows a form entry page for executing a remote interface method."
112   )
113   @HtmlDocConfig(
114      nav={
115         "<h5>Interface:  $RP{javaInterface}</h5>",
116         "<h5>Method:  $RP{javaMethod}</h5>"
117      }
118   )
119   public Div showEntryForm(
120         @Path("javaInterface") @Schema(description="Java interface name") String javaInterface,
121         @Path("javaMethod") @Schema(description="Java method name") String javaMethod
122      ) throws NotFound, Exception {
123
124      // Find the method.
125      RrpcInterfaceMethodMeta rmm = getMethods(javaInterface).get(javaMethod);
126      if (rmm == null)
127         throw new NotFound("Method not found");
128
129      Table t = table();
130
131      Type[] types = rmm.getJavaMethod().getGenericParameterTypes();
132      if (types.length == 0) {
133         t.child(tr(td("No arguments").colspan(3).style("text-align:center")));
134      } else {
135         t.child(tr(th("Index"),th("Type"),th("Value")));
136         for (int i = 0; i < types.length; i++) {
137            String type = Mutaters.toString(types[i]);
138            t.child(tr(td(i), td(type), td(input().name(String.valueOf(i)).type("text"))));
139         }
140      }
141
142      t.child(
143         tr(
144            td().colspan(3).style("text-align:right").children(
145               types.length == 0 ? null : button("reset", "Reset"),
146               button("button","Cancel").onclick("window.location.href='/'"),
147               button("submit", "Submit")
148            )
149         )
150      );
151
152      return div(form().id("form").action("request:/").method(POST).children(t));
153   }
154
155   @RestPost(
156      path="/{javaInterface}/{javaMethod}",
157      summary="Invoke an interface method",
158      description="Invoke a Java method by passing in the arguments as an array of serialized objects.\nThe returned object is then serialized to the response.",
159      swagger=@OpSwagger(
160         parameters= {
161            "{",
162               "in: 'body',",
163               "description: 'Serialized array of Java objects',",
164               "schema: {",
165                  "type': 'array'",
166               "},",
167               "examples: {",
168                  "'application/json+lax': '[\\'foo\\', 123, true]'",
169               "}",
170            "}"
171         },
172         responses= {
173            "200:{ description:'The return object serialized', schema:{type:'any'},example:{foo:123} }",
174         }
175      )
176   )
177   @HtmlDocConfig(
178      nav= {
179         "<h5>Interface:  $RP{javaInterface}</h5>",
180         "<h5>Method:  $RP{javaMethod}</h5>"
181      }
182   )
183   public Object invoke(
184         Reader r,
185         ReaderParser p,
186         @Header("Content-Type") ContentType contentType,
187         @Path("javaInterface") @Schema(description="Java interface name") String javaInterface,
188         @Path("javaMethod") @Schema(description="Java method name") String javaMethod
189      ) throws UnsupportedMediaType, NotFound, Exception {
190
191      // Find the parser.
192      if (p == null)
193         throw new UnsupportedMediaType("Could not find parser for media type ''{0}''", contentType);
194      RrpcInterfaceMeta rim = getInterfaceClass(javaInterface);
195
196      // Find the service.
197      Object service = getServiceMap().get(rim.getJavaClass());
198      if (service == null)
199         throw new NotFound("Service not found");
200
201      // Find the method.
202      RrpcInterfaceMethodMeta rmm = getMethods(javaInterface).get(javaMethod);
203      if (rmm == null)
204         throw new NotFound("Method not found");
205
206      // Parse the args and invoke the method.
207      java.lang.reflect.Method m = rmm.getJavaMethod();
208      Object[] params = p.parseArgs(r, m.getGenericParameterTypes());
209      return m.invoke(service, params);
210   }
211
212
213   //-----------------------------------------------------------------------------------------------------------------
214   // Other methods
215   //-----------------------------------------------------------------------------------------------------------------
216
217   private Map<String,RrpcInterfaceMethodMeta> getMethods(String javaInterface) throws Exception {
218      return getInterfaceClass(javaInterface).getMethodsByPath();
219   }
220
221   /**
222    * Return the <c>Class</c> given it's name if it exists in the services map.
223    */
224   private RrpcInterfaceMeta getInterfaceClass(String javaInterface) throws NotFound, Exception {
225      RrpcInterfaceMeta rm = serviceMap.get(javaInterface);
226      if (rm == null) {
227         for (Class<?> c : getServiceMap().keySet()) {
228            if (c.getName().equals(javaInterface)) {
229               rm = new RrpcInterfaceMeta(c, null);
230               serviceMap.put(javaInterface, rm);
231               return rm;
232            }
233         }
234         throw new NotFound("Interface class not found");
235      }
236      return rm;
237   }
238}