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 &gt; juneau-rest-server &gt; 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}