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;
014
015import static java.util.logging.Level.*;
016import static javax.servlet.http.HttpServletResponse.*;
017import static org.apache.juneau.internal.StringUtils.*;
018
019import java.io.*;
020import java.text.*;
021import java.util.logging.*;
022
023import javax.servlet.*;
024import javax.servlet.http.*;
025
026import org.apache.juneau.internal.*;
027import org.apache.juneau.rest.annotation.*;
028import org.apache.juneau.rest.exception.*;
029
030/**
031 * Servlet implementation of a REST resource.
032 *
033 * <h5 class='section'>See Also:</h5>
034 * <ul>
035 *    <li class='link'>{@doc juneau-rest-server.Instantiation.RestServlet}
036 * </ul>
037 */
038public abstract class RestServlet extends HttpServlet {
039
040   private static final long serialVersionUID = 1L;
041
042   private RestContextBuilder builder;
043   private volatile RestContext context;
044   private volatile Exception initException;
045   private boolean isInitialized = false;  // Should not be volatile.
046   private volatile RestResourceResolver resourceResolver;
047
048
049   @Override /* Servlet */
050   public final synchronized void init(ServletConfig servletConfig) throws ServletException {
051      try {
052         if (context != null)
053            return;
054         builder = RestContext.create(servletConfig, this.getClass(), null);
055         if (resourceResolver != null)
056            builder.resourceResolver(resourceResolver);
057         builder.init(this);
058         super.init(servletConfig);
059         builder.servletContext(this.getServletContext());
060         setContext(builder.build());
061         context.postInitChildFirst();
062      } catch (RestException e) {
063         // Thrown RestExceptions are simply caught and re-thrown on subsequent calls to service().
064         initException = e;
065         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
066      } catch (ServletException e) {
067         initException = e;
068         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
069         throw e;
070      } catch (Exception e) {
071         initException = e;
072         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
073         throw new ServletException(e);
074      } catch (Throwable e) {
075         initException = new Exception(e);
076         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
077         throw new ServletException(e);
078      }
079   }
080
081   /*
082    * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly.
083    * Used when subclasses of RestServlet are attached as child resources.
084    */
085   synchronized void innerInit(ServletConfig servletConfig) throws ServletException {
086      super.init(servletConfig);
087   }
088
089   /**
090    * Sets the context object for this servlet.
091    *
092    * @param context
093    * @throws ServletException
094    */
095   public synchronized void setContext(RestContext context) throws ServletException {
096      this.builder = context.builder;
097      this.context = context;
098      isInitialized = true;
099      context.postInit();
100   }
101
102   /**
103    * Sets the resource resolver to use for this servlet and all child servlets.
104    * <p>
105    * This method can be called immediately following object construction, but must be called before {@link #init(ServletConfig)} is called.
106    * Otherwise calling this method will have no effect.
107    *
108    * @param resourceResolver The resolver instance.  Can be <jk>null</jk>.
109    * @return This object (for method chaining).
110    */
111   public synchronized RestServlet setRestResourceResolver(RestResourceResolver resourceResolver) {
112      this.resourceResolver = resourceResolver;
113      return this;
114   }
115
116   /**
117    * Returns the path defined on this servlet if it's defined via {@link RestResource#path()}.
118    *
119    * @return The path defined on this servlet, or an empty string if not specified.
120    */
121   public synchronized String getPath() {
122      if (context != null)
123         return context.getPath();
124      for (RestResource rr : ClassUtils.getAnnotations(RestResource.class, this.getClass()))
125         if (! rr.path().isEmpty())
126            return trimSlashes(rr.path());
127      return "";
128   }
129
130   @Override /* GenericServlet */
131   public synchronized RestContextBuilder getServletConfig() {
132      return builder;
133   }
134
135   /**
136    * Returns the read-only context object that contains all the configuration information about this resource.
137    *
138    * <p>
139    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
140    * {@link #init()} is called.
141    *
142    * <p>
143    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
144    * access to this context object:
145    * <p class='bcode w800'>
146    *    <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception;
147    * </p>
148    *
149    * @return The context information on this servlet.
150    */
151   protected synchronized RestContext getContext() {
152      return context;
153   }
154
155
156   //-----------------------------------------------------------------------------------------------------------------
157   // Other methods
158   //-----------------------------------------------------------------------------------------------------------------
159
160   /**
161    * The main service method.
162    *
163    * <p>
164    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
165    */
166   @Override /* Servlet */
167   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
168      try {
169         // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check.
170         if (! isInitialized) {
171            if (initException != null) {
172               if (initException instanceof RestException)
173                  throw (RestException)initException;
174               throw new InternalServerError(initException);
175            }
176            if (context == null)
177               throw new InternalServerError("Servlet {0} not initialized.  init(ServletConfig) was not called.  This can occur if you've overridden this method but didn't call super.init(RestConfig).", getClass().getName());
178            isInitialized = true;
179         }
180
181         context.getCallHandler().service(r1, r2);
182
183      } catch (RestException e) {
184         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
185      } catch (Throwable e) {
186         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
187      }
188   }
189
190   @Override /* GenericServlet */
191   public void log(String msg) {
192      if (context != null)
193         context.getLogger().log(INFO, msg);
194   }
195
196   @Override /* GenericServlet */
197   public void log(String msg, Throwable cause) {
198      if (context != null)
199         context.getLogger().log(INFO, cause, msg);
200   }
201
202   /**
203    * Convenience method for calling <code>getContext().getLogger().log(level, msg, args);</code>
204    *
205    * @param level The log level.
206    * @param msg The message to log.
207    * @param args Optional {@link MessageFormat}-style arguments.
208    */
209   public void log(Level level, String msg, Object...args) {
210      if (context != null)
211         context.getLogger().log(level, msg, args);
212   }
213
214   /**
215    * Convenience method for calling <code>getContext().getLogger().logObjects(level, msg, args);</code>
216    *
217    * @param level The log level.
218    * @param msg The message to log.
219    * @param args Optional {@link MessageFormat}-style arguments.
220    */
221   public void logObjects(Level level, String msg, Object...args) {
222      if (context != null)
223         context.getLogger().logObjects(level, msg, args);
224   }
225
226   /**
227    * Convenience method for calling <code>getContext().getLogger().log(level, cause, msg, args);</code>
228    *
229    * @param level The log level.
230    * @param cause The cause.
231    * @param msg The message to log.
232    * @param args Optional {@link MessageFormat}-style arguments.
233    */
234   public void log(Level level, Throwable cause, String msg, Object...args) {
235      if (context != null)
236         context.getLogger().log(level, cause, msg, args);
237      else {
238         // If context failed to initialize, log to the console.
239         System.err.println(format(msg, args));
240         if (cause != null)
241            cause.printStackTrace();
242      }
243   }
244
245   /**
246    * Returns the current HTTP request.
247    *
248    * @return The current HTTP request, or <jk>null</jk> if it wasn't created.
249    */
250   public RestRequest getRequest() {
251      if (context == null)
252         return null;
253      return context.getRequest();
254   }
255
256   /**
257    * Returns the current HTTP response.
258    *
259    * @return The current HTTP response, or <jk>null</jk> if it wasn't created.
260    */
261   public RestResponse getResponse() {
262      if (context == null)
263         return null;
264      return context.getResponse();
265   }
266
267   @Override /* GenericServlet */
268   public synchronized void destroy() {
269      if (context != null)
270         context.destroy();
271      super.destroy();
272   }
273
274   /**
275    * Convenience method for calling <code>getContext().getProperties();</code>
276    *
277    * @return The resource properties as an {@link RestContextProperties}.
278    * @see RestContext#getProperties()
279    */
280   public RestContextProperties getProperties() {
281      return getContext().getProperties();
282   }
283}