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