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.rest.exception.*;
027
028/**
029 * Servlet implementation of a REST resource.
030 *
031 * <h5 class='section'>See Also:</h5>
032 * <ul>
033 *    <li class='link'>{@doc juneau-rest-server.Instantiation.RestServlet}
034 * </ul>
035 */
036public abstract class RestServlet extends HttpServlet {
037
038   private static final long serialVersionUID = 1L;
039
040   private RestContextBuilder builder;
041   private volatile RestContext context;
042   private volatile Exception initException;
043   private boolean isInitialized = false;  // Should not be volatile.
044
045   @Override /* Servlet */
046   public final synchronized void init(ServletConfig servletConfig) throws ServletException {
047      try {
048         if (context != null)
049            return;
050         builder = RestContext.create(servletConfig, this.getClass(), null).init(this);
051         super.init(servletConfig);
052         builder.servletContext(this.getServletContext());
053         setContext(builder.build());
054         context.postInitChildFirst();
055      } catch (RestException e) {
056         // Thrown RestExceptions are simply caught and re-thrown on subsequent calls to service().
057         initException = e;
058         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
059      } catch (ServletException e) {
060         initException = e;
061         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
062         throw e;
063      } catch (Exception e) {
064         initException = e;
065         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
066         throw new ServletException(e);
067      } catch (Throwable e) {
068         initException = new Exception(e);
069         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
070         throw new ServletException(e);
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
086    * @throws ServletException
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   @Override /* GenericServlet */
096   public synchronized RestContextBuilder getServletConfig() {
097      return builder;
098   }
099
100   /**
101    * Returns the read-only context object that contains all the configuration information about this resource.
102    *
103    * <p>
104    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
105    * {@link #init()} is called.
106    *
107    * <p>
108    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
109    * access to this context object:
110    * <p class='bcode w800'>
111    *    <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception;
112    * </p>
113    *
114    * @return The context information on this servlet.
115    */
116   protected synchronized RestContext getContext() {
117      return context;
118   }
119
120
121   //-----------------------------------------------------------------------------------------------------------------
122   // Other methods
123   //-----------------------------------------------------------------------------------------------------------------
124
125   /**
126    * The main service method.
127    *
128    * <p>
129    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
130    */
131   @Override /* Servlet */
132   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
133      try {
134         // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check.
135         if (! isInitialized) {
136            if (initException != null) {
137               if (initException instanceof RestException)
138                  throw (RestException)initException;
139               throw new InternalServerError(initException);
140            }
141            if (context == null)
142               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());
143            isInitialized = true;
144         }
145
146         context.getCallHandler().service(r1, r2);
147
148      } catch (RestException e) {
149         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
150      } catch (Throwable e) {
151         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
152      }
153   }
154
155   @Override /* GenericServlet */
156   public void log(String msg) {
157      if (context != null)
158         context.getLogger().log(INFO, msg);
159   }
160
161   @Override /* GenericServlet */
162   public void log(String msg, Throwable cause) {
163      if (context != null)
164         context.getLogger().log(INFO, cause, msg);
165   }
166
167   /**
168    * Convenience method for calling <code>getContext().getLogger().log(level, msg, args);</code>
169    *
170    * @param level The log level.
171    * @param msg The message to log.
172    * @param args Optional {@link MessageFormat}-style arguments.
173    */
174   public void log(Level level, String msg, Object...args) {
175      if (context != null)
176         context.getLogger().log(level, msg, args);
177   }
178
179   /**
180    * Convenience method for calling <code>getContext().getLogger().logObjects(level, msg, args);</code>
181    *
182    * @param level The log level.
183    * @param msg The message to log.
184    * @param args Optional {@link MessageFormat}-style arguments.
185    */
186   public void logObjects(Level level, String msg, Object...args) {
187      if (context != null)
188         context.getLogger().logObjects(level, msg, args);
189   }
190
191   /**
192    * Convenience method for calling <code>getContext().getLogger().log(level, cause, msg, args);</code>
193    *
194    * @param level The log level.
195    * @param cause The cause.
196    * @param msg The message to log.
197    * @param args Optional {@link MessageFormat}-style arguments.
198    */
199   public void log(Level level, Throwable cause, String msg, Object...args) {
200      if (context != null)
201         context.getLogger().log(level, cause, msg, args);
202      else {
203         // If context failed to initialize, log to the console.
204         System.err.println(format(msg, args));
205         if (cause != null)
206            cause.printStackTrace();
207      }
208   }
209
210   /**
211    * Returns the current HTTP request.
212    *
213    * @return The current HTTP request, or <jk>null</jk> if it wasn't created.
214    */
215   public RestRequest getRequest() {
216      if (context == null) 
217         return null;
218      return context.getRequest();
219   }
220
221   /**
222    * Returns the current HTTP response.
223    *
224    * @return The current HTTP response, or <jk>null</jk> if it wasn't created.
225    */
226   public RestResponse getResponse() {
227      if (context == null) 
228         return null;
229      return context.getResponse();
230   }
231
232   @Override /* GenericServlet */
233   public synchronized void destroy() {
234      if (context != null)
235         context.destroy();
236      super.destroy();
237   }
238
239   /**
240    * Convenience method for calling <code>getContext().getProperties();</code>
241    * 
242    * @return The resource properties as an {@link RestContextProperties}.
243    * @see RestContext#getProperties()
244    */
245   public RestContextProperties getProperties() {
246      return getContext().getProperties();
247   }
248}