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 RestResource#path()}.
111    *
112    * @return The path defined on this servlet, or an empty string if not specified.
113    */
114   public synchronized String getPath() {
115      if (context != null)
116         return context.getPath();
117      ClassInfo ci = ClassInfo.of(getClass());
118      for (RestResource rr : ci.getAnnotations(RestResource.class))
119         if (! rr.path().isEmpty())
120            return trimSlashes(rr.path());
121      return "";
122   }
123
124   @Override /* GenericServlet */
125   public synchronized RestContextBuilder getServletConfig() {
126      return builder;
127   }
128
129   //-----------------------------------------------------------------------------------------------------------------
130   // Context methods.
131   //-----------------------------------------------------------------------------------------------------------------
132
133   /**
134    * Returns the read-only context object that contains all the configuration information about this resource.
135    *
136    * <p>
137    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
138    * {@link #init()} is called.
139    *
140    * <p>
141    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
142    * access to this context object:
143    * <p class='bcode w800'>
144    *    <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception;
145    * </p>
146    *
147    * @return The context information on this servlet.
148    */
149   protected synchronized RestContext getContext() {
150      if (context == null)
151         throw new InternalServerError("RestContext object not set on resource.");
152      return context;
153   }
154
155   /**
156    * Convenience method for calling <c>getContext().getProperties();</c>
157    *
158    * @return The resource properties as an {@link RestContextProperties}.
159    * @see RestContext#getProperties()
160    */
161   public RestContextProperties getProperties() {
162      return getContext().getProperties();
163   }
164
165
166   //-----------------------------------------------------------------------------------------------------------------
167   // Convenience logger methods
168   //-----------------------------------------------------------------------------------------------------------------
169
170   @Override /* GenericServlet */
171   public void log(String msg) {
172      logger.info(msg);
173   }
174
175   @Override /* GenericServlet */
176   public void log(String msg, Throwable cause) {
177      logger.info(cause, msg);
178   }
179
180   /**
181    * Log a message.
182    *
183    * @param level The log level.
184    * @param msg The message to log.
185    * @param args Optional {@link MessageFormat}-style arguments.
186    */
187   public void log(Level level, String msg, Object...args) {
188      logger.log(level, msg, args);
189   }
190
191   /**
192    * Log a message.
193    *
194    * @param level The log level.
195    * @param msg The message to log.
196    * @param args Optional {@link MessageFormat}-style arguments.
197    */
198   public void logObjects(Level level, String msg, Object...args) {
199      logger.logObjects(level, msg, args);
200   }
201
202   /**
203    * Log a message.
204    *
205    * @param level The log level.
206    * @param cause The cause.
207    * @param msg The message to log.
208    * @param args Optional {@link MessageFormat}-style arguments.
209    */
210   public void log(Level level, Throwable cause, String msg, Object...args) {
211      logger.log(level, cause, msg, args);
212   }
213
214   //-----------------------------------------------------------------------------------------------------------------
215   // Lifecycle methods
216   //-----------------------------------------------------------------------------------------------------------------
217
218   /**
219    * The main service method.
220    *
221    * <p>
222    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
223    */
224   @Override /* Servlet */
225   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
226      try {
227         // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check.
228         if (! isInitialized) {
229            if (initException != null)
230               throw initException;
231            if (context == null)
232               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());
233            isInitialized = true;
234         }
235
236         context.getCallHandler().service(r1, r2);
237
238      } catch (Throwable e) {
239         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
240      }
241   }
242
243   @Override /* GenericServlet */
244   public synchronized void destroy() {
245      if (context != null)
246         context.destroy();
247      super.destroy();
248   }
249
250   //-----------------------------------------------------------------------------------------------------------------
251   // Request-time methods.
252   //-----------------------------------------------------------------------------------------------------------------
253
254   /**
255    * Returns the current HTTP request.
256    *
257    * @return The current HTTP request, or <jk>null</jk> if it wasn't created.
258    */
259   public synchronized RestRequest getRequest() {
260      return getContext().getRequest();
261   }
262
263   /**
264    * Returns the current HTTP response.
265    *
266    * @return The current HTTP response, or <jk>null</jk> if it wasn't created.
267    */
268   public synchronized RestResponse getResponse() {
269      return getContext().getResponse();
270   }
271}