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}