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
026/**
027 * Servlet implementation of a REST resource.
028 * 
029 * <h5 class='section'>See Also:</h5>
030 * <ul>
031 *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-rest-server.RestServlet">Overview &gt; juneau-rest-server &gt; RestServlet</a>
032 * </ul>
033 */
034public abstract class RestServlet extends HttpServlet {
035
036   private static final long serialVersionUID = 1L;
037
038   private RestContextBuilder builder;
039   private volatile RestContext context;
040   private boolean isInitialized = false;
041   private Exception initException;
042
043
044   @Override /* Servlet */
045   public final synchronized void init(ServletConfig servletConfig) throws ServletException {
046      try {
047         builder = new RestContextBuilder(servletConfig, this.getClass(), null);
048         builder.init(this);
049         super.init(servletConfig);
050         if (! isInitialized) {
051            builder.servletContext(this.getServletContext());
052            context = new RestContext(builder);
053            isInitialized = true;
054         }
055         context.postInit();
056         context.postInitChildFirst();
057      } catch (RestException e) {
058         // Thrown RestExceptions are simply caught and re-thrown on subsequent calls to service().
059         initException = e;
060         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
061      } catch (ServletException e) {
062         initException = e;
063         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
064         throw e;
065      } catch (Exception e) {
066         initException = e;
067         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
068         throw new ServletException(e);
069      } catch (Throwable e) {
070         initException = new Exception(e);
071         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
072         throw new ServletException(e);
073      } finally {
074         isInitialized = true;
075      }
076   }
077
078   /*
079    * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly.
080    * Used when subclasses of RestServlet are attached as child resources.
081    */
082   synchronized void innerInit(ServletConfig servletConfig) throws ServletException {
083      super.init(servletConfig);
084   }
085
086   /*
087    * Sets the context object for this servlet.
088    * Used when subclasses of RestServlet are attached as child resources.
089    */
090   synchronized void setContext(RestContext context) {
091      this.builder = context.builder;
092      this.context = context;
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'>
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, IOException {
133      try {
134         if (initException != null) {
135            if (initException instanceof RestException)
136               throw (RestException)initException;
137            throw new RestException(SC_INTERNAL_SERVER_ERROR, initException);
138         }
139         if (context == null)
140            throw new RestException(SC_INTERNAL_SERVER_ERROR, "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());
141         if (! isInitialized)
142            throw new RestException(SC_INTERNAL_SERVER_ERROR, "Servlet {0} has not been initialized", getClass().getName());
143
144         context.getCallHandler().service(r1, r2);
145
146      } catch (RestException e) {
147         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
148      } catch (Throwable e) {
149         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
150      }
151   }
152   
153   @Override /* GenericServlet */
154   public void log(String msg) {
155      if (context != null)
156         context.getLogger().log(INFO, msg);
157   }
158
159   @Override /* GenericServlet */
160   public void log(String msg, Throwable cause) {
161      if (context != null)
162         context.getLogger().log(INFO, cause, msg);
163   }
164
165   /**
166    * Convenience method for calling <code>getContext().getLogger().log(level, msg, args);</code>
167    * 
168    * @param level The log level.
169    * @param msg The message to log.
170    * @param args Optional {@link MessageFormat}-style arguments.
171    */
172   public void log(Level level, String msg, Object...args) {
173      if (context != null)
174         context.getLogger().log(level, msg, args);
175   }
176
177   /**
178    * Convenience method for calling <code>getContext().getLogger().logObjects(level, msg, args);</code>
179    * 
180    * @param level The log level.
181    * @param msg The message to log.
182    * @param args Optional {@link MessageFormat}-style arguments.
183    */
184   public void logObjects(Level level, String msg, Object...args) {
185      if (context != null)
186         context.getLogger().logObjects(level, msg, args);
187   }
188
189   /**
190    * Convenience method for calling <code>getContext().getLogger().log(level, cause, msg, args);</code>
191    * 
192    * @param level The log level.
193    * @param cause The cause.
194    * @param msg The message to log.
195    * @param args Optional {@link MessageFormat}-style arguments.
196    */
197   public void log(Level level, Throwable cause, String msg, Object...args) {
198      if (context != null)
199         context.getLogger().log(level, cause, msg, args);
200      else {
201         // If context failed to initialize, log to the console.
202         System.err.println(format(msg, args));
203         if (cause != null)
204            cause.printStackTrace();
205      }
206   }
207
208   @Override /* GenericServlet */
209   public synchronized void destroy() {
210      if (context != null)
211         context.destroy();
212      super.destroy();
213   }
214
215   /**
216    * Convenience method for calling <code>getContext().getProperties();</code>
217    * 
218    * @return The resource properties as an {@link RestContextProperties}.
219    * @see RestContext#getProperties()
220    */
221   public RestContextProperties getProperties() {
222      return getContext().getProperties();
223   }
224}