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.remoteable; 014 015import static javax.servlet.http.HttpServletResponse.*; 016import static org.apache.juneau.dto.html5.HtmlBuilder.*; 017import static org.apache.juneau.http.HttpMethodName.*; 018import static org.apache.juneau.internal.StringUtils.*; 019 020import java.lang.reflect.*; 021import java.util.*; 022import java.util.Map; 023import java.util.concurrent.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.dto.*; 027import org.apache.juneau.dto.html5.*; 028import org.apache.juneau.internal.*; 029import org.apache.juneau.parser.*; 030import org.apache.juneau.rest.*; 031import org.apache.juneau.rest.annotation.*; 032 033/** 034 * Abstract class for defining Remoteable services. 035 * 036 * <p> 037 * Remoteable services are POJOs whose methods can be invoked remotely through proxy interfaces. 038 * 039 * <p> 040 * To implement a remoteable service, developers must simply subclass from this class and implement the 041 * {@link #getServiceMap()} method that maps java interfaces to POJO instances. 042 * 043 * <h5 class='section'>See Also:</h5> 044 * <ul> 045 * <li class='link'><a class="doclink" href="../../../../../overview-summary.html#juneau-rest-server.RemoteableProxies">Overview > juneau-rest-server > Remoteable Proxies</a> 046 * </ul> 047 */ 048@SuppressWarnings("serial") 049public abstract class RemoteableServlet extends BasicRestServlet { 050 051 private final Map<String,Class<?>> classNameMap = new ConcurrentHashMap<>(); 052 053 //-------------------------------------------------------------------------------- 054 // Abstract methods 055 //-------------------------------------------------------------------------------- 056 057 /** 058 * Returns the list of interfaces to their implementation objects. 059 * 060 * <p> 061 * This class is called often and not cached, so any caching should occur in the subclass if necessary. 062 * 063 * @return The service map. 064 * @throws Exception 065 */ 066 protected abstract Map<Class<?>,Object> getServiceMap() throws Exception; 067 068 //-------------------------------------------------------------------------------- 069 // REST methods 070 //-------------------------------------------------------------------------------- 071 072 /** 073 * [GET /] - Get the list of all remote interfaces. 074 * 075 * @param req The HTTP servlet request. 076 * @return The list of links to the remote interfaces. 077 * @throws Exception 078 */ 079 @RestMethod(name=GET, path="/") 080 public List<LinkString> getInterfaces(RestRequest req) throws Exception { 081 List<LinkString> l = new LinkedList<>(); 082 boolean useAll = ! useOnlyAnnotated(); 083 for (Class<?> c : getServiceMap().keySet()) { 084 if (useAll || getContext().getBeanContext().getClassMeta(c).isRemoteable()) 085 l.add(new LinkString(c.getName(), "{0}/{1}", req.getRequestURI(), urlEncode(c.getName()))); 086 } 087 return l; 088 } 089 090 /** 091 * [GET /{javaInterface] - Get the list of all remoteable methods on the specified interface name. 092 * 093 * @param req The HTTP servlet request. 094 * @param javaInterface The Java interface name. 095 * @return The methods defined on the interface. 096 * @throws Exception 097 */ 098 @RestMethod(name=GET, path="/{javaInterface}", summary="List of available methods on $RP{javaInterface}.") 099 public Collection<LinkString> listMethods(RestRequest req, @Path("javaInterface") String javaInterface) throws Exception { 100 List<LinkString> l = new ArrayList<>(); 101 for (String s : getMethods(javaInterface).keySet()) { 102 l.add(new LinkString(s, "{0}/{1}", req.getRequestURI(), urlEncode(s))); 103 } 104 return l; 105 } 106 107 /** 108 * [GET /{javaInterface] - Get the list of all remoteable methods on the specified interface name. 109 * 110 * @param req The HTTP servlet request. 111 * @param javaInterface The Java interface name. 112 * @param javaMethod The Java method name or signature. 113 * @return A simple form entry page for invoking a remoteable method. 114 * @throws Exception 115 */ 116 @RestMethod(name=GET, path="/{javaInterface}/{javaMethod}", summary="Form entry for method $RP{javaMethod} on interface $RP{javaInterface}") 117 public Div showEntryForm(RestRequest req, @Path("javaInterface") String javaInterface, @Path("javaMethod") String javaMethod) throws Exception { 118 119 // Find the method. 120 java.lang.reflect.Method m = getMethods(javaInterface).get(javaMethod); 121 if (m == null) 122 throw new RestException(SC_NOT_FOUND, "Method not found"); 123 124 Table t = table(); 125 126 Type[] types = m.getGenericParameterTypes(); 127 if (types.length == 0) { 128 t.child(tr(td("No arguments").colspan(3).style("text-align:center"))); 129 } else { 130 t.child(tr(th("Index"),th("Type"),th("Value"))); 131 for (int i = 0; i < types.length; i++) { 132 String type = ClassUtils.toString(types[i]); 133 t.child(tr(td(i), td(type), td(input().name(String.valueOf(i)).type("text")))); 134 } 135 } 136 137 t.child( 138 tr( 139 td().colspan(3).style("text-align:right").children( 140 types.length == 0 ? null : button("reset", "Reset"), 141 button("button","Cancel").onclick("window.location.href='/'"), 142 button("submit", "Submit") 143 ) 144 ) 145 ); 146 147 return div(form().id("form").action("request:/").method(POST).children(t)); 148 } 149 150 /** 151 * [POST /{javaInterface}/{javaMethod}] - Invoke the specified service method. 152 * 153 * @param req The HTTP request. 154 * @param javaInterface The Java interface name. 155 * @param javaMethod The Java method name or signature. 156 * @return The results from invoking the specified Java method. 157 * @throws Exception 158 */ 159 @RestMethod(name=POST, path="/{javaInterface}/{javaMethod}") 160 public Object invoke(RestRequest req, @Path String javaInterface, @Path String javaMethod) throws Exception { 161 162 // Find the parser. 163 ReaderParser p = req.getBody().getReaderParser(); 164 if (p == null) 165 throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, "Could not find parser for media type ''{0}''", req.getHeaders().getContentType()); 166 Class<?> c = getInterfaceClass(javaInterface); 167 168 // Find the service. 169 Object service = getServiceMap().get(c); 170 if (service == null) 171 throw new RestException(SC_NOT_FOUND, "Service not found"); 172 173 // Find the method. 174 java.lang.reflect.Method m = getMethods(javaInterface).get(javaMethod); 175 if (m == null) 176 throw new RestException(SC_NOT_FOUND, "Method not found"); 177 178 // Parse the args and invoke the method. 179 Object[] params = p.parseArgs(req.getReader(), m.getGenericParameterTypes()); 180 return m.invoke(service, params); 181 } 182 183 184 //-------------------------------------------------------------------------------- 185 // Other methods 186 //-------------------------------------------------------------------------------- 187 188 private boolean useOnlyAnnotated() { 189 return getProperties().getBoolean(RemoteableServiceProperties.REMOTEABLE_includeOnlyRemotableMethods, false); 190 } 191 192 private Map<String,java.lang.reflect.Method> getMethods(String javaInterface) throws Exception { 193 Class<?> c = getInterfaceClass(javaInterface); 194 ClassMeta<?> cm = getContext().getBeanContext().getClassMeta(c); 195 return (useOnlyAnnotated() ? cm.getRemoteableMethods() : cm.getPublicMethods()); 196 } 197 198 /** 199 * Return the <code>Class</code> given it's name if it exists in the services map. 200 */ 201 private Class<?> getInterfaceClass(String javaInterface) throws Exception { 202 Class<?> c = classNameMap.get(javaInterface); 203 if (c == null) { 204 for (Class<?> c2 : getServiceMap().keySet()) 205 if (c2.getName().equals(javaInterface)) { 206 classNameMap.put(javaInterface, c2); 207 return c2; 208 } 209 throw new RestException(SC_NOT_FOUND, "Interface class not found"); 210 } 211 return c; 212 } 213}