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