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.rest.HttpRuntimeException.*;
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.http.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 (ServletException e) {
065         initException = e;
066         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
067         throw e;
068      } catch (Throwable e) {
069         initException = toHttpException(e, InternalServerError.class);
070         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
071      }
072   }
073
074   /*
075    * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly.
076    * Used when subclasses of RestServlet are attached as child resources.
077    */
078   synchronized void innerInit(ServletConfig servletConfig) throws ServletException {
079      super.init(servletConfig);
080   }
081
082   /**
083    * Sets the context object for this servlet.
084    *
085    * @param context Sets the context object on this servlet.
086    * @throws ServletException If error occurred during post-initialiation.
087    */
088   public synchronized void setContext(RestContext context) throws ServletException {
089      this.builder = context.builder;
090      this.context = context;
091      isInitialized = true;
092      context.postInit();
093   }
094
095   /**
096    * Sets the resource resolver to use for this servlet and all child servlets.
097    * <p>
098    * This method can be called immediately following object construction, but must be called before {@link #init(ServletConfig)} is called.
099    * Otherwise calling this method will have no effect.
100    *
101    * @param resourceResolver The resolver instance.  Can be <jk>null</jk>.
102    * @return This object (for method chaining).
103    */
104   public synchronized RestServlet setRestResourceResolver(RestResourceResolver resourceResolver) {
105      this.resourceResolver = resourceResolver;
106      return this;
107   }
108
109   /**
110    * Returns the path defined on this servlet if it's defined via {@link Rest#path()}.
111    *
112    * @return The path defined on this servlet, or an empty string if not specified.
113    */
114   @SuppressWarnings("deprecation")
115   public synchronized String getPath() {
116      if (context != null)
117         return context.getPath();
118      ClassInfo ci = ClassInfo.of(getClass());
119      for (Rest rr : ci.getAnnotations(Rest.class))
120         if (! rr.path().isEmpty())
121            return trimSlashes(rr.path());
122      for (RestResource rr : ci.getAnnotations(RestResource.class))
123         if (! rr.path().isEmpty())
124            return trimSlashes(rr.path());
125      return "";
126   }
127
128   @Override /* GenericServlet */
129   public synchronized RestContextBuilder getServletConfig() {
130      return builder;
131   }
132
133   //-----------------------------------------------------------------------------------------------------------------
134   // Context methods.
135   //-----------------------------------------------------------------------------------------------------------------
136
137   /**
138    * Returns the read-only context object that contains all the configuration information about this resource.
139    *
140    * <p>
141    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
142    * {@link #init()} is called.
143    *
144    * <p>
145    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
146    * access to this context object:
147    * <p class='bcode w800'>
148    *    <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception;
149    * </p>
150    *
151    * @return The context information on this servlet.
152    */
153   protected synchronized RestContext getContext() {
154      if (context == null)
155         throw new InternalServerError("RestContext object not set on resource.");
156      return context;
157   }
158
159   /**
160    * Convenience method for calling <c>getContext().getProperties();</c>
161    *
162    * @return The resource properties as an {@link RestContextProperties}.
163    * @see RestContext#getProperties()
164    */
165   public RestContextProperties getProperties() {
166      return getContext().getProperties();
167   }
168
169
170   //-----------------------------------------------------------------------------------------------------------------
171   // Convenience logger methods
172   //-----------------------------------------------------------------------------------------------------------------
173
174   @Override /* GenericServlet */
175   public void log(String msg) {
176      logger.info(msg);
177   }
178
179   @Override /* GenericServlet */
180   public void log(String msg, Throwable cause) {
181      logger.info(cause, msg);
182   }
183
184   /**
185    * Log a message.
186    *
187    * @param level The log level.
188    * @param msg The message to log.
189    * @param args Optional {@link MessageFormat}-style arguments.
190    */
191   public void log(Level level, String msg, Object...args) {
192      logger.log(level, msg, args);
193   }
194
195   /**
196    * Log a message.
197    *
198    * @param level The log level.
199    * @param msg The message to log.
200    * @param args Optional {@link MessageFormat}-style arguments.
201    */
202   public void logObjects(Level level, String msg, Object...args) {
203      logger.logObjects(level, msg, args);
204   }
205
206   /**
207    * Log a message.
208    *
209    * @param level The log level.
210    * @param cause The cause.
211    * @param msg The message to log.
212    * @param args Optional {@link MessageFormat}-style arguments.
213    */
214   public void log(Level level, Throwable cause, String msg, Object...args) {
215      logger.log(level, cause, msg, args);
216   }
217
218   //-----------------------------------------------------------------------------------------------------------------
219   // Lifecycle methods
220   //-----------------------------------------------------------------------------------------------------------------
221
222   /**
223    * The main service method.
224    *
225    * <p>
226    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
227    */
228   @Override /* Servlet */
229   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
230      try {
231         // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check.
232         if (! isInitialized) {
233            if (initException != null)
234               throw initException;
235            if (context == null)
236               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());
237            isInitialized = true;
238         }
239
240         context.getCallHandler().service(r1, r2);
241
242      } catch (Throwable e) {
243         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
244      }
245   }
246
247   @Override /* GenericServlet */
248   public synchronized void destroy() {
249      if (context != null)
250         context.destroy();
251      super.destroy();
252   }
253
254   //-----------------------------------------------------------------------------------------------------------------
255   // Request-time methods.
256   //-----------------------------------------------------------------------------------------------------------------
257
258   /**
259    * Returns the current HTTP request.
260    *
261    * @return The current HTTP request, or <jk>null</jk> if it wasn't created.
262    */
263   public synchronized RestRequest getRequest() {
264      return getContext().getRequest();
265   }
266
267   /**
268    * Returns the current HTTP response.
269    *
270    * @return The current HTTP response, or <jk>null</jk> if it wasn't created.
271    */
272   public synchronized RestResponse getResponse() {
273      return getContext().getResponse();
274   }
275}