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